> ## Documentation Index > Fetch the complete documentation index at: https://apidocs.document360.com/llms.txt > Use this file to discover all available pages before exploring further. # Create a variable > The `user_id` body field is required when authenticating with an M2M token unless the request includes the `X-Acting-User-Id` header (requires the `customerApi.actAsUser` scope) or the caller presents a user access token. Variables are unique by `name` within the project for the same `is_global` mode. For a global variable, the `language_code` you send is stored and returned by later `GET` calls. A later edit of the entry in the Portal clears that stored code, so `GET` then returns an empty `language_code`. ## OpenAPI ````json POST /v3/projects/{project_id}/variables { "openapi": "3.0.1", "info": { "title": "Document360 Customer API", "description": "Document360 RESTful APIs let you integrate your documentation with your software — onboard readers, manage articles, automate publishing, and more.\n\nFull reference: [API Documentation](https://apidocs.document360.io/docs).\n\n## Authentication\nV3 authenticates with an **API key** — not the `api_token` used by V1/V2. Create one in the Document360 portal under **Settings → Knowledge base portal → API keys**, then click **Create API key**. (Projects that still have the older screen show two tabs; choose **Enhanced keys (v3)**, or pick **API v3** from the Create menu.)\n\nSend it in the `X-API-Key` header:\n\n```\nX-API-Key: d360_sk_...\n```\n\nThe plaintext key is shown **once**, at creation — only its hash is stored, so a lost key must be replaced rather than recovered. Each key carries a fixed portal role, content role and content-access scope, supports an optional expiry, and can be disabled or deleted at any time from the same screen. Treat it as an opaque string: it is a pre-shared secret, not an OAuth 2.0 access token, and must not be parsed.\n\nFor flows that act on behalf of a specific person, V3 also accepts an OAuth 2.0 bearer token carrying the `customerApi` scope.\n\nAPI access is part of your subscription plan. If your plan does not include it, every endpoint returns `403` with the error code `FEATURE_NOT_IN_LICENSE` — including for keys created while an earlier plan was active. Contact your Customer Success Manager or `support@document360.com` to enable it.\n\n> **Note on terminology:** V1/V2 call these *API tokens* and manage them under **Settings → Knowledge base portal → API tokens**. V3 keys are a separate credential with their own screen, roles and scope — a V1/V2 token will not authenticate a V3 request.\n\n## Rate Limits\nAll endpoints are rate-limited per API key per project, with separate limits for reads (`GET`/`HEAD`) and writes (`POST`/`PUT`/`PATCH`/`DELETE`). Default limits vary by plan. When rate-limited, responses include `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. Use exponential backoff with jitter for optimal retry behavior.", "termsOfService": "https://document360.com/terms", "contact": { "name": "Document360 Support", "url": "https://document360.io/contact-us/", "email": "support@document360.com" }, "license": { "name": "Document360 API Terms of Use", "url": "https://document360.com/terms" }, "version": "3.0.0" }, "servers": [ { "url": "https://apihub.document360.io", "description": "Document 360 API Hub" }, { "url": "https://apihub.us.document360.io", "description": "Document360 API Hub - US data center" }, { "url": "https://apihub.{private_hosting}.document360.io", "description": "Private hosting - Please provide the subdomain name.", "variables": { "private_hosting": { "default": "domain", "description": "Sub domain for private hosting" } } } ], "security": [ { "ApiKey": [] }, { "Bearer": [ "customerApi" ] } ], "tags": [ { "name": "Projects", "description": "List and manage knowledge base projects. Export and import project documentation, and manage project-level site customization such as custom CSS and JavaScript." }, { "name": "Projects > Import & export", "description": "Start a project export or import and poll the resulting background operation for completion." }, { "name": "Workspaces", "description": "Manage workspaces and access workspace-scoped categories and articles." }, { "name": "Workspaces > AI", "description": "Query the workspace with Eddy AI, submit feedback on AI answers, and read that feedback with its conversation." }, { "name": "Articles", "description": "Create, read, update, and delete knowledge base articles and their settings, including bulk operations." }, { "name": "Articles > Publishing & workflow", "description": "Publish and unpublish articles (single and bulk) and update the article workflow status." }, { "name": "Articles > Versions", "description": "List, fork, and delete article versions." }, { "name": "Articles > Attachments", "description": "Manage the files attached to an article — list, upload, and delete article attachments." }, { "name": "Articles > Content drafts", "description": "(Internal) Stage AI-generated article content with an Accept/Reject review lifecycle — used by Eddy Copilot to preview content before committing to the live article body." }, { "name": "Articles > Labels", "description": "Read and set the labels associated with an article." }, { "name": "Articles > Comments", "description": "Read and create the comment threads authors and reviewers leave on an article's content, and reply to them. Each comment id matches the `data-comment-id` attribute of the corresponding marker in the raw article content, so adding a text-anchored comment takes two calls: update the content with the marker, then create the thread with the same id." }, { "name": "Categories", "description": "Manage the category hierarchy that organizes articles. Create, update, and delete categories and their settings, including bulk operations." }, { "name": "Categories > Page category", "description": "Read and update the content body of page-type categories, including bulk content updates." }, { "name": "Categories > Publishing & workflow", "description": "Publish and unpublish categories (single and bulk) and update the category workflow status." }, { "name": "Categories > Versions", "description": "List, fork, and delete category versions." }, { "name": "Categories > Labels", "description": "Read and set the labels associated with a category." }, { "name": "Categories > Comments", "description": "Read and create the comment threads authors and reviewers leave on a page category's content, and reply to them. Only page-type categories carry content, so other category types return `404`. Adding a text-anchored comment takes two calls: update the content with the marker, then create the thread with the same id." }, { "name": "ArticleTemplates", "description": "Manage reusable article templates — per-language article skeletons that authors and AI tools (such as Eddy Copilot) can apply when starting a new article.", "x-displayName": "Article templates" }, { "name": "Content reuse > Glossaries", "description": "Manage glossary entries — terminology definitions referenced inside articles via merge codes (`{{glossary.name}}`) and rendered as tooltips on the public KB." }, { "name": "Content reuse > Snippets", "description": "Manage reusable snippets — markdown or WYSIWYG content fragments referenced across articles via merge codes (`{{snippet.name}}`)." }, { "name": "Content reuse > Variables", "description": "Manage reusable variables — short text values referenced across articles via merge codes (`{{variable.name}}`)." }, { "name": "AiWriterStyleGuides", "description": "Retrieve AI writer style guides configured for the project — voice / tone instructions Eddy Copilot follows when generating article content. Used to let users pick a style before kicking off generation.", "x-displayName": "AI writer style guides" }, { "name": "RedirectRules", "description": "Manage article-redirection rules — 301/302 redirects from old article URLs to new locations after renames or restructures.", "x-displayName": "Redirect rules" }, { "name": "Tags", "description": "Manage tags — labels used to classify articles, categories, and files for filtering and discovery." }, { "name": "Labels", "description": "Manage labels shown on the article list and category tree. (Associating labels with a specific article or category lives under Articles and Categories.)" }, { "name": "MirroredArticles", "description": "List, read and create mirrored articles — read-only copies of a source article placed in other categories or workspaces of the project, including bulk mirroring." }, { "name": "CustomFields", "description": "Retrieve the custom field definitions configured for the project. Custom field values are read and written through the article and category settings endpoints.", "x-displayName": "Custom fields" }, { "name": "ApiReferences", "description": "Import, resync, and publish OpenAPI/Swagger specifications for API reference documentation.", "x-displayName": "API references" }, { "name": "ApiReferences > Logs", "description": "Monitor API reference import logs and inspect individual log entries." }, { "name": "Drive", "description": "Search Drive and retrieve files associated with an article, and track Drive background tasks." }, { "name": "Drive > Folders", "description": "Create, read, update, and delete Drive folders." }, { "name": "Drive > Files", "description": "Upload, copy, move, delete, and tag Drive files." }, { "name": "Drive > Image drafts", "description": "(Internal) Stage uploaded or AI-generated images (committed to Drive and associated with a staged article draft) — used by Eddy Copilot." }, { "name": "Readers", "description": "Manage reader accounts, invitations, and access scopes for private documentation." }, { "name": "Readers > Groups", "description": "Create, read, update, and delete reader groups." }, { "name": "Users", "description": "Manage users, their portal and content roles, and consolidated (effective) permissions within a project." }, { "name": "Users > Groups", "description": "Manage user groups, their portal role, and consolidated (effective) group permissions." }, { "name": "Roles", "description": "Create, read, update, and delete portal and content roles, list the users assigned to a role, and retrieve the permission dependency matrix." }, { "name": "ContentAccess", "description": "Manage per-node content-access permissions — grant, allow, deny, and inheritance overrides for users and groups against specific versions, languages, categories, or articles, plus version/language site protection.", "x-displayName": "Content access" }, { "name": "ContentAccess > Eligibility", "description": "List the readers and groups eligible to be granted content access." }, { "name": "Languages", "description": "Retrieve configured languages for a workspace." }, { "name": "Translations", "description": "Query translation status of articles across languages." }, { "name": "Operations", "description": "Track asynchronous operations — long-running tasks (bulk updates, merges, exports) accepted with `202 Accepted` and an `Operation-Location` header. Poll until the operation reaches a terminal status, honouring the `Retry-After` header." }, { "name": "AgentAssist", "description": "Back end for the Document360 Zendesk app's per-agent mode: workspace, category, article, search and Eddy AI reads executed as the signed-in support agent, plus article and category creation attributed to them.", "x-displayName": "Agent assist (Zendesk)" }, { "name": "ZendeskAppAuth", "description": "(Internal) Per-agent Document360 sign-in for the Zendesk app: begin a sign-in, receive the Identity callback, claim the agent's session handle, and check or end that session.", "x-displayName": "Zendesk app sign-in" }, { "name": "ZendeskAppAttestation", "description": "(Internal) The Zendesk app's signed background page: verifies the app token Zendesk signs for the agent at the keyboard and hands the app a short-lived agent pass that lets a sign-in complete silently.", "x-displayName": "Zendesk app attestation" }, { "name": "ZendeskAppConnect", "description": "(Internal) Completes connecting a project's Zendesk account for silent agent sign-in: the OAuth callback Zendesk redirects the approving admin to.", "x-displayName": "Zendesk account connection" }, { "name": "Analytics > Articles", "description": "View article performance metrics including views, top articles, reader locations, and category breakdowns." }, { "name": "Analytics > Search", "description": "View search usage metrics including top keywords, no-result queries, and bounced searches." }, { "name": "Analytics > AI search", "description": "View AI-powered search metrics including usage trends, most-referenced articles, and trending topics." }, { "name": "FeedbackManagement", "description": "Manage reader feedback on articles and page categories — list it, read a record, page through its team discussions, and update its status (including closing it) and assignee to sync it with external issue trackers.", "x-displayName": "Feedback management" }, { "name": "Analytics > Feedback", "description": "View reader feedback trends across articles, including top-performing articles and feedback keywords." } ], "externalDocs": { "description": "Document360 API Documentation", "url": "https://apidocs.document360.io/docs" }, "x-tagGroups": [ { "name": "Workspace", "tags": [ "Projects", "Projects > Import & export", "Workspaces", "Workspaces > AI" ] }, { "name": "Content", "tags": [ "Articles", "Articles > Publishing & workflow", "Articles > Versions", "Articles > Attachments", "Articles > Content drafts", "Articles > Labels", "Categories", "Categories > Page category", "Categories > Publishing & workflow", "Categories > Versions", "Categories > Labels", "ArticleTemplates", "AiWriterStyleGuides", "RedirectRules", "Tags", "Labels", "ApiReferences", "ApiReferences > Logs", "FeedbackManagement" ] }, { "name": "Content reuse", "tags": [ "Content reuse > Glossaries", "Content reuse > Snippets", "Content reuse > Variables" ] }, { "name": "Media", "tags": [ "Drive", "Drive > Folders", "Drive > Files", "Drive > Image drafts" ] }, { "name": "Access", "tags": [ "Readers", "Readers > Groups", "Users", "Users > Groups", "Roles", "ContentAccess", "ContentAccess > Eligibility" ] }, { "name": "Localization", "tags": [ "Languages", "Translations" ] }, { "name": "Operations", "tags": [ "Operations" ] }, { "name": "Integrations", "tags": [ "AgentAssist", "ZendeskAppAuth", "ZendeskAppAttestation", "ZendeskAppConnect" ] }, { "name": "Analytics", "tags": [ "Analytics > Articles", "Analytics > Search", "Analytics > AI search", "Analytics > Feedback" ] } ], "paths": { "/v3/projects/{project_id}/variables": { "post": { "tags": [ "Content reuse > Variables" ], "summary": "Create a variable.", "description": "The `user_id` body field is required when authenticating with an M2M token unless the\r\nrequest includes the `X-Acting-User-Id` header (requires the `customerApi.actAsUser`\r\nscope) or the caller presents a user access token. Variables are unique by `name` within\r\nthe project for the same `is_global` mode. For a global variable,\r\nthe `language_code` you send is stored and returned by later `GET` calls.\r\nA later edit of the entry in the Portal clears that stored code, so `GET` then returns an empty `language_code`.", "operationId": "createVariable", "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ], "requestBody": { "description": "The variable creation request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VariableRequest" }, "example": "
Your Zendesk account is connected to Document360. You can close this window.
", "examples": { "Global variable": { "summary": "Creates a variable with one value shared across every language. Thelanguage_code (the authoring language) is stored and returned by later GET calls.",
"value": {
"name": "product_name",
"is_global": true,
"language_code": "en",
"value": "Document360",
"languages": null,
"user_id": null
}
},
"Per-language variable": {
"summary": "Creates a variable with distinct values per language.",
"value": {
"name": "support_email",
"is_global": false,
"language_code": null,
"value": null,
"languages": [
{
"language_code": "en",
"value": "support@document360.io",
"is_main": true
},
{
"language_code": "fr",
"value": "assistance@document360.io",
"is_main": false
}
],
"user_id": null
}
}
}
}
}
},
"responses": {
"201": {
"description": "Variable created successfully.",
"headers": {
"Location": {
"description": "URL of the newly created resource.",
"schema": {
"type": "string",
"format": "uri"
}
},
"X-RateLimit-Limit": {
"description": "The maximum number of requests allowed in the current time window for this request's bucket. Read requests (GET/HEAD) and write requests (POST/PUT/PATCH/DELETE) have independent limits, applied per caller (API key or user) per project.",
"schema": {
"type": "integer",
"format": "int32"
}
},
"X-RateLimit-Remaining": {
"description": "The number of requests remaining in the current time window. When this reaches 0, subsequent requests receive a 429 response.",
"schema": {
"type": "integer",
"format": "int32"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VariableDetailResponseApiResponse"
},
"examples": {
"Variable detail": {
"summary": "Full variable including the count of articles referencing it. For a global variable, language_code is the stored authoring language.",
"value": {
"data": {
"value": "Document360",
"languages": null,
"dependency_article_count": 8,
"id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d",
"name": "product_name",
"merge_code_value": "{{variable.product_name}}",
"is_global": true,
"language_code": "en",
"created_at": "2025-01-15T09:30:00Z",
"created_by": "user_a1b2c3",
"modified_at": null,
"modified_by": null
},
"success": true,
"request_id": "req_abc123def456",
"errors": null,
"warnings": null
}
}
}
}
}
},
"401": {
"description": "Authentication token is missing or invalid.",
"headers": {
"WWW-Authenticate": {
"description": "Indicates the authentication scheme required. Returns `Bearer` with optional `error` and `error_description` parameters per RFC 6750.",
"schema": {
"type": "string"
}
}
},
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/unauthorized"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/unauthorized` for 401 responses."
},
"status": {
"enum": [
401
],
"type": "integer",
"description": "HTTP status code. Always `401` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Missing or invalid token": {
"summary": "Authentication token is missing or invalid.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/unauthorized",
"title": "Unauthorized.",
"status": 401,
"detail": "The authentication token is missing or has expired.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Bearer token is missing or invalid.",
"field": null,
"details": null
}
],
"warnings": null
}
}
}
}
}
},
"403": {
"description": "Token lacks the required content permission, or the project's license plan does not include this feature.",
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/forbidden"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/forbidden` for 403 responses."
},
"status": {
"enum": [
403
],
"type": "integer",
"description": "HTTP status code. Always `403` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Insufficient permissions": {
"summary": "Insufficient permissions for this resource.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/forbidden",
"title": "Forbidden.",
"status": 403,
"detail": "You do not have permission to perform this action.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "FORBIDDEN",
"message": "Insufficient permissions for this project.",
"field": null,
"details": null
}
],
"warnings": null
}
}
}
}
}
},
"409": {
"description": "A variable with the same `name` already exists in the project for the same `is_global` mode.",
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/conflict"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/conflict` for 409 responses."
},
"status": {
"enum": [
409
],
"type": "integer",
"description": "HTTP status code. Always `409` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Conflict": {
"summary": "The operation conflicts with the current state of the resource.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/conflict",
"title": "Conflict.",
"status": 409,
"detail": "The operation conflicts with the current state of the resource.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "CONFLICT",
"message": "A resource with the same identifier already exists.",
"field": null,
"details": null
}
],
"warnings": null
}
}
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/validation-error"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/validation-error` for 422 responses."
},
"status": {
"enum": [
422
],
"type": "integer",
"description": "HTTP status code. Always `422` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Validation failed": {
"summary": "One or more request fields failed validation.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "One or more fields failed validation.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "VALIDATION_ERROR",
"message": "The field is required.",
"field": "name",
"details": null
}
],
"warnings": null
}
}
}
}
}
},
"400": {
"description": "The request body is malformed or contains invalid JSON.",
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/bad-request"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/bad-request` for 400 responses."
},
"status": {
"enum": [
400
],
"type": "integer",
"description": "HTTP status code. Always `400` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Malformed request body": {
"summary": "The request body could not be parsed.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/bad-request",
"title": "Bad Request.",
"status": 400,
"detail": "The request body is malformed or contains invalid JSON.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "BAD_REQUEST",
"message": "Could not parse the request body. Ensure it is valid JSON.",
"field": null,
"details": null
}
],
"warnings": null
}
}
}
}
}
},
"429": {
"description": "Rate limit exceeded. Retry after the duration specified in the Retry-After header.",
"headers": {
"Retry-After": {
"description": "Number of seconds to wait before retrying the request. Use exponential backoff with jitter for optimal retry behavior.",
"schema": {
"type": "integer",
"format": "int32"
}
},
"X-RateLimit-Limit": {
"description": "The maximum number of requests allowed in the current time window for this request's bucket. Read requests (GET/HEAD) and write requests (POST/PUT/PATCH/DELETE) have independent limits, applied per caller (API key or user) per project. Read limits are typically higher than write limits.",
"schema": {
"type": "integer",
"format": "int32"
}
},
"X-RateLimit-Remaining": {
"description": "The number of requests remaining in the current time window. When this reaches 0, subsequent requests will receive a 429 response.",
"schema": {
"type": "integer",
"format": "int32"
}
},
"X-RateLimit-Reset": {
"description": "Number of seconds until the current rate limit window resets.",
"schema": {
"type": "integer",
"format": "int64"
}
}
},
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/too-many-requests"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/too-many-requests` for 429 responses."
},
"status": {
"enum": [
429
],
"type": "integer",
"description": "HTTP status code. Always `429` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Rate limit exceeded": {
"summary": "Rate limit exceeded.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/too-many-requests",
"title": "Too Many Requests.",
"status": 429,
"detail": "Rate limit exceeded. Retry after the duration specified in the Retry-After header.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "TOO_MANY_REQUESTS",
"message": "Rate limit exceeded. Retry after the duration specified in the Retry-After header.",
"field": null,
"details": null
}
],
"warnings": null
}
}
}
}
}
},
"500": {
"description": "An unexpected server error occurred.",
"content": {
"application/problem+json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/V3ProblemDetails"
},
{
"type": "object",
"properties": {
"type": {
"enum": [
"https://apidocs.document360.io/apidocs/errors/internal-server-error"
],
"type": "string",
"description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/internal-server-error` for 500 responses."
},
"status": {
"enum": [
500
],
"type": "integer",
"description": "HTTP status code. Always `500` for this response.",
"format": "int32"
}
}
}
]
},
"examples": {
"Unexpected server error": {
"summary": "Unexpected server error.",
"value": {
"type": "https://apidocs.document360.io/apidocs/errors/internal-server-error",
"title": "Internal Server Error.",
"status": 500,
"detail": "An unexpected error occurred. Please try again or contact support.",
"instance": null,
"trace_id": "req_abc123def456",
"errors": [
{
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred.",
"field": null,
"details": null
}
],
"warnings": null
}
}
}
}
}
}
},
"security": [
{
"ApiKey": []
},
{
"Bearer": [
"customerApi"
]
}
]
}
}
},
"components": {
"parameters": {
"ProjectId": {
"name": "project_id",
"in": "path",
"description": "The unique identifier of the project. Retrieve project IDs from `GET /v3/projects`.",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d"
}
}
},
"schemas": {
"VariableRequest": {
"required": [
"is_global",
"name"
],
"type": "object",
"properties": {
"name": {
"minLength": 1,
"type": "string",
"description": "Internal name of the variable. Must be unique within the project for the same `is_global` mode.",
"example": "product_name"
},
"is_global": {
"type": "boolean",
"description": "When `true`, the variable applies to every language. When `false`, supply per-language\r\nvalues via `languages`."
},
"language_code": {
"type": "string",
"description": "Language code the global value was authored in. Required when `is_global` is `true`.",
"nullable": true
},
"value": {
"type": "string",
"description": "Variable value used when `is_global` is `true`.",
"nullable": true
},
"languages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VariableLanguageEntry"
},
"description": "Per-language entries. Required when `is_global` is `false`.",
"nullable": true
},
"user_id": {
"type": "string",
"description": "User ID to record as creator / modifier when authenticating with an M2M token. Ignored when the\r\ncaller presents a user access token. For M2M callers with the `customerApi.actAsUser` scope,\r\nprefer the `X-Acting-User-Id` header.",
"nullable": true
}
},
"additionalProperties": false,
"description": "Request body for creating or replacing a variable. Used by both `POST` and `PUT` endpoints."
},
"VariableDetailResponseApiResponse": {
"required": [
"data",
"request_id",
"success"
],
"type": "object",
"properties": {
"data": {
"allOf": [
{
"$ref": "#/components/schemas/VariableDetailResponse"
}
],
"description": "Response data payload."
},
"success": {
"type": "boolean",
"description": "Whether the API request was successful.",
"readOnly": true
},
"request_id": {
"minLength": 1,
"type": "string",
"description": "Unique identifier for request tracing and correlation.",
"readOnly": true
},
"errors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiError"
},
"description": "List of errors if the request failed.",
"nullable": true
},
"warnings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiWarning"
},
"description": "List of non-fatal warnings from the request.",
"nullable": true
}
},
"additionalProperties": false,
"description": "Generic API response wrapper containing typed data."
},
"V3ProblemDetails": {
"required": [
"status",
"title",
"type"
],
"type": "object",
"properties": {
"type": {
"minLength": 1,
"enum": [
"https://apidocs.document360.io/apidocs/errors/bad-request",
"https://apidocs.document360.io/apidocs/errors/unauthorized",
"https://apidocs.document360.io/apidocs/errors/forbidden",
"https://apidocs.document360.io/apidocs/errors/resource-not-found",
"https://apidocs.document360.io/apidocs/errors/conflict",
"https://apidocs.document360.io/apidocs/errors/validation-error",
"https://apidocs.document360.io/apidocs/errors/too-many-requests",
"https://apidocs.document360.io/apidocs/errors/internal-server-error"
],
"type": "string",
"description": "URI reference identifying the error type (links to documentation)."
},
"title": {
"minLength": 1,
"type": "string",
"description": "Short human-readable summary of the error type."
},
"status": {
"enum": [
400,
401,
403,
404,
409,
422,
429,
500
],
"type": "integer",
"description": "HTTP status code.",
"format": "int32"
},
"detail": {
"type": "string",
"description": "Human-readable explanation specific to this occurrence.",
"nullable": true
},
"instance": {
"type": "string",
"description": "URI of the request that generated the error.",
"nullable": true
},
"trace_id": {
"type": "string",
"description": "Request trace identifier for correlation.",
"nullable": true
},
"errors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiError"
},
"description": "Structured list of specific errors (extension field).",
"nullable": true
},
"warnings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiWarning"
},
"description": "Non-fatal warnings (extension field).",
"nullable": true
}
},
"additionalProperties": false,
"description": "RFC 7807 Problem Details response for V3 API errors.\r\nContent-Type: application/problem+json"
},
"VariableLanguageEntry": {
"required": [
"language_code"
],
"type": "object",
"properties": {
"language_code": {
"minLength": 1,
"type": "string",
"description": "ISO language code (e.g. `en`, `fr`) for this entry."
},
"value": {
"type": "string",
"description": "Variable value for this language. Plain text or simple HTML.",
"nullable": true
},
"is_main": {
"type": "boolean",
"description": "Whether this language is the authored source from which other languages are translated.\r\nAt most one entry should have `is_main = true`."
}
},
"additionalProperties": false,
"description": "One language-specific value within a non-global variable."
},
"VariableDetailResponse": {
"required": [
"dependency_article_count",
"id",
"is_global",
"name"
],
"type": "object",
"properties": {
"value": {
"type": "string",
"description": "Variable value used when `is_global` is `true`. Plain text or simple HTML.\r\nReturned only by single-resource (detail) endpoints, not in list responses.",
"nullable": true
},
"languages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VariableLanguageEntry"
},
"description": "Per-language entries used when `is_global` is `false`. Returned only by single-resource\r\n(detail) endpoints, not in list responses.",
"nullable": true
},
"dependency_article_count": {
"type": "integer",
"description": "Total number of articles that currently reference this variable across all languages.",
"format": "int32",
"readOnly": true
},
"id": {
"type": "string",
"description": "Unique identifier of the variable.",
"format": "uuid",
"nullable": true,
"readOnly": true,
"example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d"
},
"name": {
"type": "string",
"description": "Internal name of the variable. Must be unique within the project.",
"nullable": true,
"example": "product_name"
},
"merge_code_value": {
"type": "string",
"description": "Auto-generated merge code used to reference this variable inside article content\r\n(for example `{{variable.product_name}}`).",
"nullable": true,
"readOnly": true
},
"is_global": {
"type": "boolean",
"description": "When `true`, the variable has a single value that applies to every language. When `false`,\r\nthe variable carries one entry per language in the `languages` array."
},
"language_code": {
"type": "string",
"description": "Language code (e.g. `en`, `fr`) the global value was authored in. Populated only when\r\n`is_global` is `true`.",
"nullable": true
},
"created_at": {
"type": "string",
"description": "Date and time when the variable was created.",
"format": "date-time",
"nullable": true,
"readOnly": true
},
"created_by": {
"type": "string",
"description": "User ID of the creator.",
"nullable": true,
"readOnly": true
},
"modified_at": {
"type": "string",
"description": "Date and time when the variable was last modified.",
"format": "date-time",
"nullable": true,
"readOnly": true
},
"modified_by": {
"type": "string",
"description": "User ID of the last modifier.",
"nullable": true,
"readOnly": true
}
},
"additionalProperties": false,
"description": "Detailed variable representation returned by single-resource endpoints."
},
"ApiError": {
"required": [
"code",
"message"
],
"type": "object",
"properties": {
"code": {
"minLength": 1,
"enum": [
"ACCESS_DENIED",
"ARCHIVED_CONTENT_READ_ONLY",
"ARCHIVE_ALREADY_ARCHIVED",
"ARCHIVE_NOT_ARCHIVED",
"BAD_REQUEST",
"CONFLICT",
"FEATURE_NOT_IN_LICENSE",
"FEEDBACK_CLOSED",
"FORBIDDEN",
"INTERNAL_SERVER_ERROR",
"LICENSE_LIMIT_EXCEEDED",
"OPERATION_FAILED",
"PERMISSION_DENIED",
"PREMIUM_FEATURE_NOT_IN_LICENSE",
"RESOURCE_NOT_FOUND",
"TOO_MANY_REQUESTS",
"UNAUTHORIZED",
"UNRECOGNIZED_FIELD",
"UNRECOGNIZED_FIELDS",
"VALIDATION_ERROR"
],
"type": "string",
"description": "Machine-readable error code (e.g. VALIDATION_ERROR, RESOURCE_NOT_FOUND)."
},
"message": {
"minLength": 1,
"type": "string",
"description": "Human-readable error message."
},
"field": {
"type": "string",
"description": "The request field that caused the error, if applicable.",
"nullable": true
},
"details": {
"type": "string",
"description": "Additional context about the error.",
"nullable": true
}
},
"additionalProperties": false,
"description": "Represents an error returned by the API."
},
"ApiWarning": {
"required": [
"code",
"message"
],
"type": "object",
"properties": {
"code": {
"minLength": 1,
"type": "string",
"description": "Machine-readable warning code."
},
"message": {
"minLength": 1,
"type": "string",
"description": "Human-readable warning message."
}
},
"additionalProperties": false,
"description": "Represents a non-fatal warning from the API."
}
},
"securitySchemes": {
"ApiKey": {
"type": "apiKey",
"description": "API key for machine-to-machine integrations, webhooks, and CI/CD (recommended). This is a static, pre-shared secret - treat it as an opaque string and do not parse it; it is an API key, not an OAuth 2.0 access token. Create one in the portal under **Settings > Knowledge base portal > API keys** using the **Create API key** button - this is a different credential from the V1/V2 *API token*, which lives on its own screen and will not authenticate a V3 request. The plaintext key (format `d360_sk_...`) is shown only once, at creation. Send it in the `X-API-Key` header: `X-API-Key: d360_sk_...`. Each key carries a fixed portal role, content role, and content-access scope, supports an optional expiry, and can be disabled or deleted at any time from the portal.",
"name": "X-API-Key",
"in": "header"
},
"Bearer": {
"type": "oauth2",
"description": "All V3 endpoints require a Bearer token. Generate tokens in the Document360 portal under **Settings > API Tokens**. Tokens are project-scoped, require the `customerApi` scope, and do not expire by default. Tokens can be revoked at any time from the portal. Include the token in every request: `Authorization: Bearer