> ## 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. # Update an article > Partial update — only fields included in the request body are modified; omitted fields remain unchanged. **Reorder within the same category:** send `order` (0-based position among sibling articles; lower numbers appear higher in the list). **Move to a different category:** send `category_id` (the destination category's id), optionally with `order` to position it there. Combine both to move and position in one request. **Content and title updates:** `content` and `title` are both stored on the article version. When `auto_fork` is true and the target version is published, a new draft version is automatically created and the update is applied to the draft. If `auto_fork` is false (default), changing `content` on a published version returns a 422 error, while changing only the `title` updates the published version in place, as renaming an article in the portal does. Fields that are not version-scoped — `order`, `category_id`, `hidden`, `translation_option` — apply to the article itself and never require a fork. When authenticating with an API key or API token, send `user_id` together with `auto_fork: true`; it identifies the author of the new draft. OAuth user tokens don't need it. ### Related endpoints **Next** - `POST /v3/projects/{project_id}/articles/{article_id}/publish` — Publishes the edit. Content changes stay in the draft until the article is published. **See also** - `GET /v3/projects/{project_id}/articles/{article_id}` — Reads the article back. Use `content_mode=raw` to see the stored source rather than the rendered form. ### Adding a comment starts here (step 1 of 2) This endpoint writes the content. It is also **step 1 of adding a comment** — see `POST /v3/projects/{project_id}/articles/{article_id}/comments` for step 2. A comment is **two facts that must agree**: a marker in the content that highlights the annotated text, and the comment thread itself. Two endpoints write them, and both calls must carry the **same `comment_id`**. 1. **Add the marker** — `PATCH /v3/projects/{project_id}/articles/{article_id}` Send the content with the annotated text wrapped in an element carrying `data-comment-id=""`. Generate the id yourself (a GUID) — you reuse it in step 2. 2. **Create the thread** — `POST /v3/projects/{project_id}/articles/{article_id}/comments` Send that same value as `comment_id` in the body. **Order matters.** Step 2 checks the marker is present and returns `422` if it is not, so a thread can never exist without the text it annotates. The reverse is not enforced: a marker with no thread is an inert highlight, so if step 2 fails, either retry it or take the marker back out. The request examples on both operations use the same `comment_id` (`b7f3c1a0-5e42-4d18-9a6c-2f8091d3e547`) and the same annotated text (`single sign-on`), so you can copy step 1, then step 2, and the pair works unchanged. Comments are supported only on block-editor (Editor 3.0) content; any other editor is rejected with `422`. ## OpenAPI ````json PATCH /v3/projects/{project_id}/articles/{article_id} { "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}/articles/{article_id}": { "patch": { "tags": [ "Articles" ], "summary": "Update an article.", "description": "Partial update — only fields included in the request body are modified; omitted fields remain unchanged.\r\n \r\n**Reorder within the same category:** send `order` (0-based position among sibling articles;\r\nlower numbers appear higher in the list).\r\n \r\n**Move to a different category:** send `category_id` (the destination category's id),\r\noptionally with `order` to position it there. Combine both to move and position in one request.\r\n \r\n**Content and title updates:** `content` and `title` are both stored on the article version.\r\nWhen `auto_fork` is true and the target version is published, a new draft version is automatically\r\ncreated and the update is applied to the draft. If `auto_fork` is false (default), changing\r\n`content` on a published version returns a 422 error, while changing only the `title` updates\r\nthe published version in place, as renaming an article in the portal does. Fields that are not\r\nversion-scoped — `order`, `category_id`, `hidden`, `translation_option` — apply to\r\nthe article itself and never require a fork.\r\n \r\nWhen authenticating with an API key or API token, send `user_id` together with\r\n`auto_fork: true`; it identifies the author of the new draft. OAuth user tokens don't need it.\n\n### Related endpoints\n\n**Next**\n- `POST /v3/projects/{project_id}/articles/{article_id}/publish` — Publishes the edit. Content changes stay in the draft until the article is published.\n\n**See also**\n- `GET /v3/projects/{project_id}/articles/{article_id}` — Reads the article back. Use `content_mode=raw` to see the stored source rather than the rendered form.\n\n### Adding a comment starts here (step 1 of 2)\n\nThis endpoint writes the content. It is also **step 1 of adding a comment** — see\n`POST /v3/projects/{project_id}/articles/{article_id}/comments` for step 2.\n\nA comment is **two facts that must agree**: a marker in the content that highlights the annotated text, and\nthe comment thread itself. Two endpoints write them, and both calls must carry the **same `comment_id`**.\n\n1. **Add the marker** — `PATCH /v3/projects/{project_id}/articles/{article_id}`\n Send the content with the annotated text wrapped in an element carrying `data-comment-id=\"\"`.\n Generate the id yourself (a GUID) — you reuse it in step 2.\n2. **Create the thread** — `POST /v3/projects/{project_id}/articles/{article_id}/comments`\n Send that same value as `comment_id` in the body.\n\n**Order matters.** Step 2 checks the marker is present and returns `422` if it is not, so a thread can never\nexist without the text it annotates. The reverse is not enforced: a marker with no thread is an inert\nhighlight, so if step 2 fails, either retry it or take the marker back out.\n\nThe request examples on both operations use the same `comment_id` (`b7f3c1a0-5e42-4d18-9a6c-2f8091d3e547`) and\nthe same annotated text (`single sign-on`), so you can copy step 1, then step 2, and\nthe pair works unchanged.\n\nComments are supported only on block-editor (Editor 3.0) content; any other editor is rejected with `422`.", "operationId": "updateArticle", "parameters": [ { "$ref": "#/components/parameters/ProjectId" }, { "$ref": "#/components/parameters/ArticleId" }, { "$ref": "#/components/parameters/LangCode" } ], "requestBody": { "description": "Updated article data.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateArticleRequest" }, "example": "Document360

Your Zendesk account is connected to Document360. You can close this window.

", "examples": { "Update title and content with auto-fork": { "summary": "Updates the article title and content. When auto_fork is true and the target version is published, a new draft version is created automatically, authored by user_id. user_id is required with auto_fork when using an API key; it is ignored for OAuth user tokens.", "value": { "title": "Getting Started with Single Sign-On (Updated)", "content": "# Introduction\nThis updated guide walks you through configuring SSO for your organization.", "content_type": null, "category_id": null, "hidden": null, "version_number": null, "translation_option": null, "source": null, "order": null, "auto_fork": true, "user_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" } }, "Step 1 of 2 — add a comment marker to the content": { "summary": "Adding a comment takes **two calls**, and this is the first: put the marker in the content, then create the thread with `POST /v3/projects/{projectId}/articles/{articleId}/comments` using the *Step 2 of 2* example there. Both examples use the same `comment_id` (`b7f3c1a0-5e42-4d18-9a6c-2f8091d3e547`), so you can send this body and then that one unchanged.\n\nWrap the annotated text in an element carrying `data-comment-id=\"\"`, where the id is a GUID you generate. The id is repeated in `class` because that is what Editor 3.0 emits and what it styles the highlight from.\n\n**Comments are supported only on block-editor articles** — a markdown or wysiwyg article stores the marker but renders it as an inert highlight with no discussion attached, so the create endpoint rejects those with `422`. This step is never optional: every comment this API creates is text-anchored, so the marker check always applies.", "value": { "title": "Getting Started with Single Sign-On", "content": "

Enable single sign-on before inviting readers.

", "content_type": null, "category_id": null, "hidden": null, "version_number": null, "translation_option": null, "source": null, "order": null, "auto_fork": null, "user_id": null } }, "Update an Editor 3.0 article with DFM markdown": { "summary": "Articles written in Editor 3.0 are stored as `block`, and `GET` returns their DFM markdown in `content` and their HTML in `html_content`. To send DFM back — a GET-then-update round trip — set `content_type` to `markdown`; the server renders it and stores both forms. Without it, `content` is read as HTML, as for any block article, and DFM sent that way is rejected with `422`. `wysiwyg` is not accepted for Editor 3.0 articles. Other articles ignore `content_type`.", "value": { "title": null, "content": "## Configure the identity provider\n\nOpen **Settings** and select Single sign-on.\n\n- Copy the metadata URL\n- Paste it into your identity provider\n", "content_type": "markdown", "category_id": null, "hidden": null, "version_number": null, "translation_option": null, "source": null, "order": null, "auto_fork": null, "user_id": null } }, "Reorder an article within its category": { "summary": "Moves the article to position 0 (top) among the other articles in its category, without changing content or category.", "value": { "title": null, "content": null, "content_type": null, "category_id": null, "hidden": null, "version_number": null, "translation_option": null, "source": null, "order": 0, "auto_fork": null, "user_id": null } }, "Move article to a different category": { "summary": "Moves the article to a new category and positions it there. Combine category_id with order to move and reorder in one request.", "value": { "title": null, "content": null, "content_type": null, "category_id": "f4a5b6c7-d8e9-0a1b-2c3d-4e5f6a7b8c9d", "hidden": null, "version_number": null, "translation_option": null, "source": null, "order": 3, "auto_fork": null, "user_id": null } }, "Update a specific version": { "summary": "Targets a specific draft version for the update. Use version_number to specify which version to update.", "value": { "title": "Getting Started with SSO - v3 Draft", "content": "# Introduction\nDraft version with additional sections.", "content_type": null, "category_id": null, "hidden": null, "version_number": 3, "translation_option": null, "source": null, "order": null, "auto_fork": null, "user_id": null } } } } } }, "responses": { "200": { "description": "Article updated successfully.", "headers": { "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/ArticleDetailResponseApiResponse" }, "examples": { "Article updated successfully": { "summary": "The article has been updated. If auto_fork was true and the version was published, the response shows the newly created draft version.", "value": { "data": { "id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d", "title": "Getting Started with Single Sign-On (Updated)", "content": "# Introduction\nThis updated guide walks you through configuring SSO for your organization.", "html_content": null, "category_id": "f4a5b6c7-d8e9-0a1b-2c3d-4e5f6a7b8c9d", "workspace_id": "1c2d3e4f-5a6b-7c8d-9e0f-a1b2c3d4e5f6", "version_number": 3, "public_version": 2, "latest_version": 3, "enable_rtl": false, "hidden": false, "is_archived": false, "status": "draft", "version_type": null, "order": 0, "created_by": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "authors": [], "created_at": "2025-06-01T09:00:00Z", "modified_at": "2025-08-16T10:00:00Z", "modified_by": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "slug": "getting-started-with-single-sign-on", "is_fallback_content": false, "description": null, "category_type": null, "content_type": "markdown", "is_shared_article": false, "translation_option": "none", "url": null, "preview_url": null, "current_workflow_status_id": null, "lang_code": "en", "available_languages": null, "settings": null, "stale_status": null, "content_hash": "sha256:9f2c5a1d8e3b4c6f7a0918273645dacb5e1f2a3b4c5d6e7f8091a2b3c4d5e6f7" }, "success": true, "request_id": "req_abc123def456", "errors": null, "warnings": null } } } } }, "links": { "publishArticle": { "operationId": "publishArticle", "description": "Publishes the edit. Content changes stay in the draft until the article is published." }, "addComment": { "operationId": "createArticleComment", "parameters": { "project_id": "$request.path.project_id", "article_id": "$request.path.article_id" }, "description": "Step 2 — create the comment thread for the marker just written into this article, using the same comment_id." } } }, "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 scope or content permission.", "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 } } } } } }, "404": { "description": "Article not found.", "content": { "application/problem+json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/V3ProblemDetails" }, { "type": "object", "properties": { "type": { "enum": [ "https://apidocs.document360.io/apidocs/errors/resource-not-found" ], "type": "string", "description": "RFC 7807 error type URI. Always `https://apidocs.document360.io/apidocs/errors/resource-not-found` for 404 responses." }, "status": { "enum": [ 404 ], "type": "integer", "description": "HTTP status code. Always `404` for this response.", "format": "int32" } } } ] }, "examples": { "Resource not found": { "summary": "The requested resource does not exist.", "value": { "type": "https://apidocs.document360.io/apidocs/errors/resource-not-found", "title": "Resource not found.", "status": 404, "detail": "The requested resource was not found.", "instance": null, "trace_id": "req_abc123def456", "errors": [ { "code": "RESOURCE_NOT_FOUND", "message": "Resource not found.", "field": null, "details": null } ], "warnings": null } } } } } }, "409": { "description": "The article's language is archived and cannot be modified.", "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 } }, "Missing user_id with auto_fork": { "summary": "An API key or API token (M2M) sent auto_fork: true without user_id. The key has no user of its own, so a user is needed as the author of the new draft.", "value": { "type": "https://apidocs.document360.io/apidocs/errors/validation-error", "title": "Validation Error", "status": 422, "detail": "user_id is required when auto_fork is true and the request is not made with a user token.", "instance": null, "trace_id": "req_abc123def456", "errors": [ { "code": "VALIDATION_ERROR", "message": "user_id is required when auto_fork is true and the request is not made with a user token.", "field": "user_id", "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" ] } ], "x-related-endpoints": [ { "relationship": "related", "method": "GET", "path": "/v3/projects/{project_id}/articles/{article_id}", "operationId": "getArticle", "description": "Reads the article back. Use `content_mode=raw` to see the stored source rather than the rendered form." }, { "relationship": "next", "method": "POST", "path": "/v3/projects/{project_id}/articles/{article_id}/publish", "operationId": "publishArticle", "description": "Publishes the edit. Content changes stay in the draft until the article is published." } ] } } }, "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" } }, "ArticleId": { "name": "article_id", "in": "path", "description": "The unique identifier of the article. Retrieve article IDs from `GET /v3/projects/{projectId}/articles`.", "required": true, "schema": { "type": "string", "format": "uuid", "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" } }, "LangCode": { "name": "lang_code", "in": "query", "description": "ISO 639-1 language code (e.g., `en`, `fr`). Defaults to the project's primary language if omitted.", "schema": { "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$", "type": "string", "format": "language-tag", "example": "en" } } }, "schemas": { "UpdateArticleRequest": { "type": "object", "properties": { "title": { "type": "string", "description": "The updated title of the article.", "nullable": true, "example": "Getting Started with Single Sign-On (Updated)" }, "content": { "type": "string", "description": "The updated body content of the article. Markdown for markdown articles; HTML for block or wysiwyg articles. For HTML content the server also derives and stores a markdown representation automatically.", "nullable": true, "example": "# Introduction\\nThis updated guide walks you through configuring SSO." }, "content_type": { "allOf": [ { "$ref": "#/components/schemas/ContentType" } ], "description": "The format of `content`, for articles written in Editor 3.0 (which are always stored as `block`): `block` (the default when omitted) — `content` is the article HTML; `markdown` — `content` is DFM markdown, the format GET returns in `content`, so a GET-then-update round trip sends it back with `markdown`. `wysiwyg` is rejected with a 422. Ignored for all other articles, which keep their stored format.", "nullable": true, "example": "markdown" }, "category_id": { "type": "string", "description": "The id of the category to move the article to. Combine with `order` to position it in the destination. Retrieve category IDs from `GET /v3/projects/{projectId}/workspaces/{workspaceId}/categories`.", "nullable": true, "example": "f4a5b6c7-d8e9-0a1b-2c3d-4e5f6a7b8c9d" }, "hidden": { "type": "boolean", "description": "Whether the article should be hidden from readers.", "nullable": true, "example": false }, "version_number": { "type": "integer", "description": "The version number to apply the update to. Defaults to the latest version if omitted. When `auto_fork` is true and this version is published, the update is applied to the newly created draft version, not the original published version.", "format": "int32", "nullable": true, "example": 3 }, "translation_option": { "allOf": [ { "$ref": "#/components/schemas/TranslationOption" } ], "description": "The updated translation status of the article. A value that is not a defined status (for example the number `7`) is rejected with a 422.", "nullable": true }, "source": { "type": "string", "description": "The source of the update. Common values: `API`, `import`, `portal`. When omitted, defaults to `API` for Customer API requests.", "nullable": true, "example": "API" }, "order": { "type": "integer", "description": "The updated 0-based display position within the category (lower = higher in the list).", "format": "int32", "nullable": true, "example": 5 }, "auto_fork": { "type": "boolean", "description": "When true and the target version is published, a new draft version is automatically forked and the update (including content and title) is applied to the new draft. When the target version is the unpublished version of an unpublished article (which is read-only), the unpublished version is forked to a new draft in the same way and the update is applied to the new draft; the article stays unpublished until the new draft is published. When the target version is already a draft, no fork occurs and the update is applied directly. When false (default), updating `content` on a published version returns a 422 error, while a title-only update is applied to the published version in place (as in the portal). Fields that are not version-scoped (`order`, `category_id`, `hidden`, `translation_option`) apply to the article itself and never require a fork. With an API key or API token (M2M), also send `user_id` — the request is rejected with 422 without it.", "nullable": true, "example": true }, "user_id": { "type": "string", "description": "The identifier of the user making the update. Required when `auto_fork` is true and the request is authenticated with an API key or API token (M2M) without `X-Acting-User-Id` — the user becomes the author of the forked draft version and must have permission to update the article. Optional otherwise. When using a user access token (OAuth), this field is ignored — the user ID is resolved from the token. Retrieve user IDs from `GET /v3/projects/{projectId}/users`.", "nullable": true, "example": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" } }, "additionalProperties": false, "description": "Request to update an existing article." }, "ArticleDetailResponseApiResponse": { "required": [ "data", "request_id", "success" ], "type": "object", "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/ArticleDetailResponse" } ], "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" }, "ContentType": { "enum": [ "markdown", "wysiwyg", "block" ], "type": "string", "description": "The editor content type used for an article.", "x-enumNames": [ "Markdown", "Wysiwyg", "Block" ], "x-enum-varnames": [ "Markdown", "Wysiwyg", "Block" ], "x-ms-enum": { "name": "ContentType", "modelAsString": true } }, "TranslationOption": { "enum": [ "none", "needTranslation", "translated", "inProgress" ], "type": "string", "description": "The translation status of an article.", "x-enumNames": [ "None", "NeedTranslation", "Translated", "InProgress" ], "x-enum-varnames": [ "None", "NeedTranslation", "Translated", "InProgress" ], "x-ms-enum": { "name": "TranslationOption", "modelAsString": true } }, "ArticleDetailResponse": { "required": [ "created_at", "enable_rtl", "hidden", "id", "is_archived", "is_fallback_content", "is_shared_article", "latest_version", "modified_at", "order", "status", "title", "version_number" ], "type": "object", "properties": { "id": { "type": "string", "description": "The unique identifier of the article.", "format": "uuid", "nullable": true, "readOnly": true, "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "title": { "type": "string", "description": "The title of the article.", "nullable": true, "example": "Getting Started with Single Sign-On" }, "content": { "type": "string", "description": "The raw content of the article in its source format. For an article written in Editor 3.0 this is its DFM markdown and `html_content` is its HTML: to send the DFM back in an update, set `content_type` to `markdown`.", "nullable": true, "example": "# Introduction\\nThis guide walks you through configuring SSO for your organization." }, "html_content": { "type": "string", "description": "The rendered HTML content of the article.", "nullable": true, "example": "

Introduction

This guide walks you through configuring SSO.

" }, "category_id": { "type": "string", "description": "The identifier of the category this article belongs to. Use this value with `GET /v3/projects/{projectId}/categories/{categoryId}` to retrieve category details.", "nullable": true, "example": "f4a5b6c7-d8e9-0a1b-2c3d-4e5f6a7b8c9d" }, "workspace_id": { "type": "string", "description": "The workspace this article belongs to. Corresponds to a workspace from `GET /v3/projects/{projectId}/workspaces`.", "nullable": true, "example": "1c2d3e4f-5a6b-7c8d-9e0f-a1b2c3d4e5f6" }, "version_number": { "type": "integer", "description": "The current version number of the article.", "format": "int32", "example": 3 }, "public_version": { "type": "integer", "description": "The latest published version number, or null if the article has never been published.", "format": "int32", "nullable": true, "readOnly": true, "example": 2 }, "latest_version": { "type": "integer", "description": "The latest version number including drafts.", "format": "int32", "readOnly": true, "example": 3 }, "enable_rtl": { "type": "boolean", "description": "Whether right-to-left text direction is enabled.", "example": false }, "hidden": { "type": "boolean", "description": "Whether the article is hidden from readers.", "example": false }, "is_archived": { "type": "boolean", "description": "Whether this language of the article is archived. Resolved from the requested (or default)\r\nlanguage; a language with no archive record is never archived.", "readOnly": true, "example": false }, "status": { "enum": [ "draft", "published", "unpublished" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/ArticleStatus" } ], "description": "The publication status of the article.", "x-enumNames": [ "Draft", "Published", "Unpublished" ], "x-enum-varnames": [ "Draft", "Published", "Unpublished" ], "x-ms-enum": { "name": "ArticleStatus", "modelAsString": true } }, "version_type": { "type": "string", "description": "Derived version discriminator (version-less collab): `draft`, `published`, or `system_backup`. Null when collaborative editing is not enabled for the project. Display/system-backup ordinals are exposed only by the versions-list endpoint (they are relative to the full version set).", "nullable": true, "readOnly": true, "example": "draft" }, "order": { "type": "integer", "description": "The display order of the article within its category.", "format": "int32", "example": 5 }, "created_by": { "type": "string", "description": "The user ID of the original article creator. For full author details including name and avatar, see the `authors` array. Corresponds to a user from `GET /v3/projects/{projectId}/users`.", "nullable": true, "example": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" }, "authors": { "type": "array", "items": { "$ref": "#/components/schemas/AuthorResponse" }, "description": "The list of contributors to this article, including the original creator and any subsequent editors.", "nullable": true }, "created_at": { "type": "string", "description": "The date and time the article was created.", "format": "date-time", "readOnly": true, "example": "2025-06-01T09:00:00Z" }, "modified_at": { "type": "string", "description": "The date and time the article was last modified.", "format": "date-time", "readOnly": true, "example": "2025-08-15T14:30:00Z" }, "modified_by": { "type": "string", "description": "The user ID of who last modified this article. Corresponds to a user from `GET /v3/projects/{projectId}/users`.", "nullable": true, "readOnly": true, "example": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e" }, "slug": { "type": "string", "description": "The URL slug for the article.", "nullable": true, "readOnly": true, "example": "getting-started-with-single-sign-on" }, "is_fallback_content": { "type": "boolean", "description": "Whether the content is a fallback from the default language.", "example": false }, "description": { "type": "string", "description": "A brief description of the article.", "nullable": true, "example": "Learn how to configure single sign-on authentication for your knowledge base." }, "category_type": { "allOf": [ { "$ref": "#/components/schemas/CategoryType" } ], "description": "The type of the category this article belongs to: `folder`, `page` or `index`.\r\nThe value is read from the parent category in the language of this response (`lang_code`). When\r\nthat category has no version in this language, the type cannot be read and `folder` is returned.\r\nIn a `PATCH` response the value is read after the update, so it is the type of the new category\r\nwhen the request sends `category_id`. It is `null` only in the rare case that the article\r\ncannot be read again after the update.", "nullable": true }, "content_type": { "allOf": [ { "$ref": "#/components/schemas/ContentType" } ], "description": "The editor content type of the article.", "nullable": true }, "is_shared_article": { "type": "boolean", "description": "Whether the article is shared across multiple projects.", "example": false }, "translation_option": { "enum": [ "none", "needTranslation", "translated", "inProgress" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/TranslationOption" } ], "description": "The translation status of the article.", "x-enumNames": [ "None", "NeedTranslation", "Translated", "InProgress" ], "x-enum-varnames": [ "None", "NeedTranslation", "Translated", "InProgress" ], "x-ms-enum": { "name": "TranslationOption", "modelAsString": true } }, "url": { "type": "string", "description": "Canonical public URL of the article on the knowledge base site. Deterministic from the slug\r\nand always returned, but only resolves to live content once the article is published; a\r\ndraft-only article returns 404 (or redirects to login) at this URL until first publish.\r\nUse `preview_url` to view unpublished content.", "format": "uri", "nullable": true, "example": "https://docs.example.com/docs/getting-started-with-single-sign-on" }, "preview_url": { "type": "string", "description": "Preview URL for the current version of the article on the knowledge base site. Works before\r\npublishing; anonymous readers are redirected to login. For an already-published article it\r\nrenders the requested version's content. Empty for legacy KB v1 projects.", "nullable": true, "example": "https://docs.example.com/v1/docs/9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d?isPreview=true&versionNumber=3" }, "current_workflow_status_id": { "type": "string", "description": "The current workflow status identifier. Retrieve available statuses from `GET /v3/projects/{projectId}/workflow-statuses`.", "nullable": true, "example": "b7e2a1d4-3f56-4c89-9d0e-1a2b3c4d5e6f" }, "lang_code": { "type": "string", "description": "The language code of this article version.", "nullable": true, "example": "en" }, "available_languages": { "type": "array", "items": { "$ref": "#/components/schemas/AvailableLanguage" }, "description": "The other languages this article is available in, with the public URL of the article in each language.\r\nDoes not include the language of this response (`lang_code`). When the article is read with\r\n`published=true`, only languages that have a published version are listed. An empty list means the\r\narticle has no other language version. `url` is `null` when the URL of that language cannot be resolved,\r\nfor example when the language is not configured on the workspace version.", "nullable": true }, "settings": { "allOf": [ { "$ref": "#/components/schemas/ArticleDocumentSettings" } ], "description": "A subset of article settings (SEO title, description, external search exclusion). For full settings including tags, related articles, and display options, use `GET /v3/projects/{projectId}/articles/{articleId}/settings`.", "nullable": true }, "stale_status": { "allOf": [ { "$ref": "#/components/schemas/StaleStatusResponse" } ], "description": "The content freshness status of the article: whether it is up to date or due for review, along with the review schedule. Mirrors the `stale_status` exposed by the category endpoints.", "nullable": true }, "content_hash": { "type": "string", "description": "SHA-256 hash of this version's stored content, prefixed with the algorithm\r\n(`sha256:`). Changes only when the content itself changes — record it at write\r\ntime and compare against listing or detail reads to detect drift without downloading\r\ncontent. `null` for versions saved before hash tracking was introduced.", "nullable": true, "readOnly": true, "example": "sha256:9f2c5a1d8e3b4c6f7a0918273645dacb5e1f2a3b4c5d6e7f8091a2b3c4d5e6f7" }, "custom_fields_data": { "type": "object", "additionalProperties": { "nullable": true }, "description": "Read-only map of custom field name → display value for this article (dropdown /\r\nmulti-select values are resolved to option labels; unset fields are `null`).\r\nPopulated on the single-article and article-version GET endpoints only. Named to match the\r\nV1/V2 `customFieldsData` map, and kept distinct from the `custom_fields` array on\r\nthe settings endpoints, which is where raw values are read and written.\r\nAbsent from the response entirely for article types that have no custom-field surface (for\r\nexample custom pages); an empty map means custom fields apply but none are configured.", "nullable": true, "readOnly": true } }, "additionalProperties": false, "description": "Full article detail with content." }, "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." }, "ArticleStatus": { "enum": [ "draft", "published", "unpublished" ], "type": "string", "description": "The publication status of an article.", "x-enumNames": [ "Draft", "Published", "Unpublished" ], "x-enum-varnames": [ "Draft", "Published", "Unpublished" ], "x-ms-enum": { "name": "ArticleStatus", "modelAsString": true } }, "AuthorResponse": { "required": [ "email", "id" ], "type": "object", "properties": { "id": { "type": "string", "description": "The unique identifier of the author.", "format": "uuid", "nullable": true, "readOnly": true, "example": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" }, "first_name": { "type": "string", "description": "The first name of the author.", "nullable": true, "example": "Jane" }, "last_name": { "type": "string", "description": "The last name of the author.", "nullable": true, "example": "Doe" }, "email": { "type": "string", "description": "The email address of the author.", "format": "email", "nullable": true, "example": "jane.doe@example.com" }, "profile_logo_url": { "type": "string", "description": "The URL of the author's profile image.", "nullable": true, "example": "https://cdn.example.com/avatars/jane-doe.png" } }, "additionalProperties": false, "description": "Author information for an article." }, "CategoryType": { "enum": [ "folder", "page", "index" ], "type": "string", "description": "The type of category an article belongs to.", "x-enumNames": [ "Folder", "Page", "Index" ], "x-enum-varnames": [ "Folder", "Page", "Index" ], "x-ms-enum": { "name": "CategoryType", "modelAsString": true } }, "AvailableLanguage": { "type": "object", "properties": { "lang_code": { "type": "string", "description": "The language code (e.g., \"en\", \"fr\").", "nullable": true, "example": "fr" }, "url": { "type": "string", "description": "The URL of the article in this language.", "format": "uri", "nullable": true, "example": "https://docs.example.com/docs/fr/getting-started-with-single-sign-on" }, "translation_status": { "enum": [ "none", "needTranslation", "translated", "inProgress" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/TranslationOption" } ], "description": "The translation status for this language.", "x-enumNames": [ "None", "NeedTranslation", "Translated", "InProgress" ], "x-enum-varnames": [ "None", "NeedTranslation", "Translated", "InProgress" ], "x-ms-enum": { "name": "TranslationOption", "modelAsString": true } } }, "additionalProperties": false, "description": "Represents a language in which an article is available." }, "ArticleDocumentSettings": { "type": "object", "properties": { "seo_title": { "type": "string", "description": "The custom SEO title for search engines.", "nullable": true, "example": "SSO Setup Guide - Product Documentation" }, "description": { "type": "string", "description": "The meta description for search engines.", "nullable": true, "example": "Step-by-step instructions for configuring single sign-on with SAML or OIDC providers." }, "exclude_from_external_search": { "type": "boolean", "description": "Whether the article is excluded from external search engine indexing.", "example": false } }, "additionalProperties": false, "description": "SEO and display settings embedded within an article detail." }, "StaleStatusResponse": { "required": [ "expire_days", "is_from_document_settings" ], "type": "object", "properties": { "article_stale_status": { "enum": [ "fresh", "stale" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/ArticleStaleStatus" } ], "description": "Whether the article is up to date (fresh) or requires review (stale).", "x-enumNames": [ "Fresh", "Stale" ], "x-enum-varnames": [ "Fresh", "Stale" ], "x-ms-enum": { "name": "ArticleStaleStatus", "modelAsString": true } }, "stale_reason": { "type": "string", "description": "The reason the article was marked stale, if any.", "nullable": true }, "expired_at": { "type": "string", "description": "The date the article is next due for review (UTC). Once this date passes, the article is considered stale.", "format": "date-time", "nullable": true, "example": "2026-01-31T05:10:26Z" }, "is_from_document_settings": { "type": "boolean", "description": "Whether the review schedule was inherited from project-level documentation settings.", "example": false }, "expire_days": { "type": "integer", "description": "The number of days from publication after which the article should next be reviewed.", "format": "int32", "example": 30 } }, "additionalProperties": false, "description": "Content freshness information for an article, derived from its review reminder schedule." }, "ArticleStaleStatus": { "enum": [ "fresh", "stale" ], "type": "string", "description": "The content freshness classification of an article, derived from its review reminder schedule.", "x-enumNames": [ "Fresh", "Stale" ], "x-enum-varnames": [ "Fresh", "Stale" ], "x-ms-enum": { "name": "ArticleStaleStatus", "modelAsString": true } } }, "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 `. Alternatively, use the Authorize button below to sign in via OAuth2 Authorization Code flow with PKCE.", "flows": { "authorizationCode": { "authorizationUrl": "https://identity.document360.io/connect/authorize", "tokenUrl": "https://identity.document360.io/connect/token", "scopes": { "openid": "OpenID Connect", "profile": "User profile", "email": "User email", "customerApi": "Document360 Customer API" }, "x-usePkce": { "disableManualConfiguration": true, "hideClientSecretInput": true } } }, "x-d360-clientId": "apiHubWebClient" } } } } ````