> ## 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. # Bulk update articles > Performs partial updates on multiple articles in a single request. Each article update is independent; if one fails, the others are still applied. Only fields included in each item are modified; omitted fields remain unchanged. Maximum 100 articles per request. **Per-item results:** the response is always `200` with one result per item; check each item's `success` flag. Items are applied in order and committed individually, so a failure part-way through does not roll back the items that already succeeded. **Content and title updates:** `auto_fork` is honoured per item and behaves exactly as it does on `PATCH /articles/{articleId}` — with `auto_fork: true`, a `content` or `title` change on a published version creates a draft and applies the update there. Without it, a `content` change on a published version fails for that item while the rest of the batch proceeds, and a title-only change updates the published version in place. 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. A missing or unknown `user_id` is reported as one `422` that names each failing item (for example `articles[0].user_id`), before any item is written. ## OpenAPI ````json PATCH /v3/projects/{project_id}/articles/bulk { "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/bulk": { "patch": { "tags": [ "Articles" ], "summary": "Bulk update articles.", "description": "Performs partial updates on multiple articles in a single request. Each article update is\r\nindependent; if one fails, the others are still applied. Only fields included in each item\r\nare modified; omitted fields remain unchanged. Maximum 100 articles per request.\r\n \r\n**Per-item results:** the response is always `200` with one result per item; check each item's\r\n`success` flag. Items are applied in order and committed individually, so a failure part-way\r\nthrough does not roll back the items that already succeeded.\r\n \r\n**Content and title updates:** `auto_fork` is honoured per item and behaves exactly as it does\r\non `PATCH /articles/{articleId}` — with `auto_fork: true`, a `content` or `title`\r\nchange on a published version creates a draft and applies the update there. Without it, a\r\n`content` change on a published version fails for that item while the rest of the batch proceeds,\r\nand a title-only change updates the published version in place.\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.\r\nA missing or unknown `user_id` is reported as one `422` that names each failing item\r\n(for example `articles[0].user_id`), before any item is written.", "operationId": "bulkUpdateArticles", "parameters": [ { "$ref": "#/components/parameters/ProjectId" } ], "requestBody": { "description": "Bulk update details. Maximum 100 articles per request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkUpdateArticleRequest" }, "example": "
Your Zendesk account is connected to Document360. You can close this window.
", "examples": { "Bulk update two articles": { "summary": "Updates multiple articles in a single request. Each item must include the article_id. Maximum 100 articles per request. auto_fork is per item: the first article changes its title, which is version-scoped, so it forks a draft, authored by that item's user_id, rather than rewriting the published version. The second changes only visibility, needs no fork, and so needs no user_id.", "value": { "articles": [ { "article_id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d", "lang_code": "en", "title": "Getting Started with SSO (Updated)", "content": null, "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" }, { "article_id": "c5d6e7f8-9a0b-1c2d-3e4f-5a6b7c8d9e0f", "lang_code": "en", "title": null, "content": null, "content_type": null, "category_id": null, "hidden": true, "version_number": null, "translation_option": null, "source": null, "order": null, "auto_fork": null, "user_id": null } ] } } } } } }, "responses": { "200": { "description": "Articles updated successfully. Per-item failures (e.g. an archived language) are reported in the item's `details` field, not as a whole-batch error.", "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/BulkUpdateOperationResultListApiResponse" }, "examples": { "Bulk update completed": { "summary": "Results for each article in the bulk update request. Each item reports its index, article id, and language code so you can tell exactly which article/language succeeded or failed.", "value": { "data": [ { "lang_code": "en", "index": 0, "id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d", "success": true, "details": "Successfully updated article with an id 9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, { "lang_code": "de", "index": 1, "id": "c5d6e7f8-9a0b-1c2d-3e4f-5a6b7c8d9e0f", "success": true, "details": "Successfully updated article with an id c5d6e7f8-9a0b-1c2d-3e4f-5a6b7c8d9e0f" } ], "success": true, "request_id": "req_abc123def456", "errors": null, "warnings": null } }, "Bulk update with a failed language": { "summary": "The top-level success is false when any item fails. Use the index, id, and lang_code fields to identify which article/language failed.", "value": { "data": [ { "lang_code": "en", "index": 0, "id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d", "success": true, "details": "Successfully updated article with an id 9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, { "lang_code": "fr", "index": 1, "id": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d", "success": false, "details": "Failed to update article with an id 9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d\nError: Article not found for the specified language." } ], "success": false, "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.", "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": "One or more articles 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 } } } } } }, "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 on a bulk item with auto_fork": { "summary": "With an API key, an item sets auto_fork: true without user_id. The field names the item index, and no item is written.", "value": { "type": "https://apidocs.document360.io/apidocs/errors/validation-error", "title": "Validation Error", "status": 422, "detail": "articles[0].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": "articles[0].user_id is required when auto_fork is true and the request is not made with a user token.", "field": "articles[0].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" } } }, "schemas": { "BulkUpdateArticleRequest": { "required": [ "articles" ], "type": "object", "properties": { "articles": { "maxItems": 100, "type": "array", "items": { "$ref": "#/components/schemas/BulkUpdateArticleItem" }, "description": "The list of articles to update. Maximum 100 items per request." } }, "additionalProperties": false, "description": "Request to bulk update articles." }, "BulkUpdateOperationResultListApiResponse": { "required": [ "data", "request_id", "success" ], "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/BulkUpdateOperationResult" }, "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" }, "BulkUpdateArticleItem": { "required": [ "article_id", "lang_code" ], "type": "object", "properties": { "article_id": { "minLength": 1, "type": "string", "description": "The unique identifier of the article to update. Retrieve article IDs from `GET /v3/projects/{projectId}/workspaces/{workspaceId}/articles`.", "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "lang_code": { "minLength": 1, "type": "string", "description": "The language code for the article version to update.", "example": "en" }, "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": "A single article update item within a bulk update request." }, "BulkUpdateOperationResult": { "required": [ "id", "index", "success" ], "type": "object", "properties": { "lang_code": { "type": "string", "description": "The language code of the item version that was updated.", "nullable": true, "example": "en" }, "index": { "type": "integer", "description": "The zero-based position of this item in the original request array, enabling correlation of results to input items even when the operation fails and no ID is available.", "format": "int32", "example": 0 }, "id": { "type": "string", "description": "The identifier of the item that was processed. May be null for failed create operations where no resource was created.", "format": "uuid", "nullable": true, "readOnly": true, "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "success": { "type": "boolean", "description": "Whether the operation succeeded for this item.", "readOnly": true, "example": true }, "details": { "type": "string", "description": "Additional details or error message for the operation.", "nullable": true, "example": "Article created successfully." } }, "additionalProperties": false, "description": "Result of a bulk content update on a single language-scoped item. Extends Document360.Customer.API.Models.V3.BulkOperationResult\r\nwith the language code so callers can tell which language version succeeded or failed." }, "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." }, "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 } } }, "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