> ## 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. # Fork a category version > Creates a new draft version copied from an existing version of the category. The request body is optional: when omitted (or when `version_number` is null), the currently published version is forked. Provide `version_number` to fork a specific historical version instead — list available version numbers from `GET /v3/projects/{projectId}/categories/{categoryId}/versions`. If the category has no published version and no `version_number` is given, a 422 error is returned. Returns `409 Conflict` if a draft version already exists for this category. The newly created draft can then be edited via `PATCH /v3/projects/{projectId}/categories/{categoryId}/content`. The response includes the full content of the new draft version; its `created_at` is the date and time the new version was created. The author of the new draft is resolved in this order: the user access token (OAuth), then the `X-Acting-User-Id` header (needs the `customerApi.actAsUser` scope), then `user_id` in the body, which must be a user of this project. `user_id` is optional: with an API key or API token (M2M) and no `user_id`, the user who created the category is recorded as the author of the fork. If that user is no longer a user of this project, a 422 naming `user_id` is returned before anything is written; send `user_id` instead. ## OpenAPI ````json POST /v3/projects/{project_id}/categories/{category_id}/fork { "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}/categories/{category_id}/fork": { "post": { "tags": [ "Categories > Versions" ], "summary": "Fork a category version.", "description": "Creates a new draft version copied from an existing version of the category. The request body is\r\noptional: when omitted (or when `version_number` is null), the currently published version is forked.\r\nProvide `version_number` to fork a specific historical version instead — list available version\r\nnumbers from `GET /v3/projects/{projectId}/categories/{categoryId}/versions`. If the category has\r\nno published version and no `version_number` is given, a 422 error is returned. Returns `409 Conflict`\r\nif a draft version already exists for this category. The newly created draft can then be edited via\r\n`PATCH /v3/projects/{projectId}/categories/{categoryId}/content`. The response includes the full content\r\nof the new draft version; its `created_at` is the date and time the new version was created.\r\n\n\r\nThe author of the new draft is resolved in this order: the user access token (OAuth), then the\r\n`X-Acting-User-Id` header (needs the `customerApi.actAsUser` scope), then `user_id` in the\r\nbody, which must be a user of this project. `user_id` is optional: with an API key or API token (M2M)\r\nand no `user_id`, the user who created the category is recorded as the author of the fork. If that\r\nuser is no longer a user of this project, a 422 naming `user_id` is returned before anything is\r\nwritten; send `user_id` instead.\r\n", "operationId": "forkCategoryVersion", "parameters": [ { "$ref": "#/components/parameters/ProjectId" }, { "$ref": "#/components/parameters/CategoryId" }, { "$ref": "#/components/parameters/LangCode" } ], "requestBody": { "description": "Optional fork options. When omitted, the currently published version is forked.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForkVersionRequest" }, "example": "Document360

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

", "examples": { "Fork the currently published version (default)": { "summary": "Omit the body entirely, or send an empty object. The currently published version is forked into a new draft.", "value": { "version_number": null, "user_id": null } }, "Fork a specific version": { "summary": "Forks version 2 into a new draft. List available version numbers from the corresponding versions endpoint.", "value": { "version_number": 2, "user_id": null } }, "Fork as a project user (API key / M2M)": { "summary": "With an API key or API token (M2M) and no X-Acting-User-Id header, send user_id: that project user becomes the author of the new draft. Required for an article fork (422 without it). Optional for a category fork: without it, the user who created the category becomes the author.", "value": { "version_number": 2, "user_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" } } } } } }, "responses": { "201": { "description": "Category version forked 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/CategoryContentResponseApiResponse" }, "examples": { "Category version forked successfully": { "summary": "A new draft version has been created from the published content. The response shows the new draft version. created_at is the date and time when this new version was created.", "value": { "data": { "id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d", "title": "Getting Started", "content": "# Getting Started\nWelcome to our product! This guide will help you set up your account and begin using the platform.", "html_content": "

Getting Started

\n

Welcome to our product! This guide will help you set up your account and begin using the platform.

", "parent_category_id": null, "workspace_id": "1c2d3e4f-5a6b-7c8d-9e0f-a1b2c3d4e5f6", "version_number": 3, "public_version": 2, "latest_version": 3, "enable_rtl": false, "hidden": false, "status": "draft", "version_type": null, "created_by": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "created_at": "2025-08-16T10:00:00Z", "modified_at": "2025-08-16T10:00:00Z", "modified_by": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", "slug": "getting-started", "is_fallback_content": false, "content_type": "markdown", "current_workflow_status_id": null, "stale_status": null, "content_hash": "sha256:9f2c5a1d8e3b4c6f7a0918273645dacb5e1f2a3b4c5d6e7f8091a2b3c4d5e6f7" }, "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 scope or content permission, including the publish permission needed to change `hidden`.", "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": "Category or requested version 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": "Category already has a draft version, or the category's type carries no content to fork - only Page categories do.", "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, the body `user_id` is not a project user, no `user_id` was sent and the category's creator is no longer a project user, or the category has no published version to fork.", "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 } }, "Creator is no longer a project user (API key / M2M)": { "summary": "An API key or API token (M2M) sent no user_id, so the item's creator would be recorded, but that user has left the project. Send user_id. Nothing is written.", "value": { "type": "https://apidocs.document360.io/apidocs/errors/validation-error", "title": "Validation Error", "status": 422, "detail": "user_id is required: no user was sent, and the creator of category 'c5d6e7f8-9a0b-1c2d-3e4f-5a6b7c8d9e0f', who is recorded when no user is sent, is no longer a user of this project.", "instance": null, "trace_id": "req_abc123def456", "errors": [ { "code": "VALIDATION_ERROR", "message": "user_id is required: no user was sent, and the creator of category 'c5d6e7f8-9a0b-1c2d-3e4f-5a6b7c8d9e0f', who is recorded when no user is sent, is no longer a user of this project.", "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" ] } ] } } }, "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" } }, "CategoryId": { "name": "category_id", "in": "path", "description": "The unique identifier of the category. Retrieve category IDs from `GET /v3/projects/{projectId}/categories`.", "required": true, "schema": { "type": "string", "format": "uuid", "example": "b4c5d6e7-f8a9-0b1c-2d3e-4f5a6b7c8d9e" } }, "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": { "ForkVersionRequest": { "type": "object", "properties": { "version_number": { "maximum": 2147483647, "minimum": 1, "type": "integer", "description": "The version number to fork. When omitted or null, the currently published version is forked. For an article that has no published version, the unpublished version is forked. List available versions from the corresponding versions endpoint.", "format": "int32", "nullable": true, "example": 2 }, "user_id": { "type": "string", "description": "The identifier of the user who forks the version. This user becomes the author of the new draft version and must be a user of this project. When using a user access token (OAuth) or the `X-Acting-User-Id` header, this field is ignored — the user ID is resolved from the token or header. With an API key or API token (M2M): article fork — required; the request is rejected with 422 without it. Category fork — optional; when it is not sent, the user who created the category is recorded as the author of the fork, and if that user is no longer a user of this project the request is rejected with 422 naming `user_id`. Retrieve user IDs from `GET /v3/projects/{projectId}/users`.", "nullable": true, "example": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" } }, "additionalProperties": false, "description": "Request to fork an article or category version. The entire body is optional —\r\nwhen omitted, the currently published version is forked." }, "CategoryContentResponseApiResponse": { "required": [ "data", "request_id", "success" ], "type": "object", "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/CategoryContentResponse" } ], "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" }, "CategoryContentResponse": { "required": [ "created_at", "enable_rtl", "hidden", "id", "is_fallback_content", "latest_version", "modified_at", "public_version", "status", "title", "version_number" ], "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier of the category.", "format": "uuid", "nullable": true, "readOnly": true, "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "title": { "type": "string", "description": "Title of the category.", "nullable": true }, "content": { "type": "string", "description": "Markdown content of the category.", "nullable": true }, "html_content": { "type": "string", "description": "HTML-rendered content of the category.", "nullable": true }, "parent_category_id": { "type": "string", "description": "Identifier of the parent category, if any. Retrieve category IDs from `GET /v3/projects/{projectId}/workspaces/{workspaceId}/categories`.", "nullable": true }, "workspace_id": { "type": "string", "description": "Workspace this category belongs to. Corresponds to a workspace from `GET /v3/projects/{projectId}/workspaces`.", "nullable": true }, "version_number": { "type": "integer", "description": "Current version number of the content.", "format": "int32" }, "public_version": { "type": "integer", "description": "Published version number.", "format": "int32", "readOnly": true }, "latest_version": { "type": "integer", "description": "Latest available version number.", "format": "int32", "readOnly": true }, "enable_rtl": { "type": "boolean", "description": "Whether right-to-left text direction is enabled." }, "hidden": { "type": "boolean", "description": "Whether the category is hidden from readers." }, "status": { "enum": [ "draft", "published", "unpublished" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/ArticleStatus" } ], "description": "Publication status of the category.", "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" }, "created_by": { "type": "string", "description": "User ID of the creator. Corresponds to a user from `GET /v3/projects/{projectId}/users`.", "nullable": true }, "created_at": { "type": "string", "description": "Date and time the category was created. When you get or fork a specific category version, this is the date and time that version was created.", "format": "date-time", "readOnly": true }, "modified_at": { "type": "string", "description": "Date and time the category was last modified.", "format": "date-time", "readOnly": true }, "modified_by": { "type": "string", "description": "User ID of who last modified this category. Corresponds to a user from `GET /v3/projects/{projectId}/users`.", "nullable": true, "readOnly": true }, "slug": { "type": "string", "description": "URL-friendly slug for the category.", "nullable": true, "readOnly": true }, "is_fallback_content": { "type": "boolean", "description": "Whether the content is a fallback from the default language." }, "content_type": { "allOf": [ { "$ref": "#/components/schemas/ContentType" } ], "description": "Content format type.", "nullable": true }, "current_workflow_status_id": { "type": "string", "description": "Current workflow status identifier, if workflows are enabled. Retrieve available statuses from `GET /v3/projects/{projectId}/workflow-statuses`.", "nullable": true }, "stale_status": { "allOf": [ { "$ref": "#/components/schemas/StaleStatusResponse" } ], "description": "The content freshness status of the page category: whether it is up to date or due for review, along with the review schedule. Mirrors the `stale_status` exposed by the article 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 later reads to detect drift without downloading content.\r\n`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 page category (dropdown /\r\nmulti-select values are resolved to option labels; unset fields are `null`).\r\nPopulated on the category-content and category-version GET endpoints only. Named to match\r\nthe V1/V2 `customFieldsData` map, and kept distinct from the `custom_fields` array\r\non the settings endpoints, which is where raw values are read and written.", "nullable": true, "readOnly": true } }, "additionalProperties": false, "description": "Category content with versioning details." }, "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 } }, "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 } }, "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" } } } } ````