> ## 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.
# Import an API reference
> Imports an API specification (OpenAPI/Swagger) into the specified workspace. Supply the spec through
exactly ONE of three sources:
The workspace is validated before the import begins; a `422` is returned if the workspace does not exist
or is not enabled for API documentation. Requires the `UpdateArticles` permission.
The import runs in the background. This endpoint ACCEPTS the work and returns
`202 Accepted` immediately — the fetch, parse and article generation all happen afterwards. The
response body carries `operation_id` and `operation_status: "queued"`, and the
`Operation-Location` response header holds the URL to poll. Track it with
`GET /v3/projects/{projectId}/operations/{operationId}` until its status is terminal; the import's
own outcome — including any parser errors and warnings — arrives in that operation's `result`.
There is no longer a `200` on this action, and specification problems that used to come back as a
`422` now surface through the operation instead.
One job per project. A project may have only one import or resync open at a time, because the
commit path is not transactional. A second, different request while one is open is refused with
`409 Conflict` naming the operation to poll. Re-submitting an IDENTICAL request is not a conflict —
it returns the operation already running rather than starting a second one.
Polling needs a different permission from starting. This action requires
`articles.update`, a CONTENT role permission, while
`GET /v3/projects/{projectId}/operations/{operationId}` requires `projectSettings.view`, a
PORTAL role permission. They are separate families and neither implies the other, so a token that can
start an import may receive an `Operation-Location` it cannot read. Grant both to any integration
that imports and then polls.
This is a property of the shared operations endpoint rather than of this action:
`POST /v3/projects/{projectId}/categories/{categoryId}/archive` has the same split
(`categories.delete` to start, `projectSettings.view` to poll) and predates this route.
## OpenAPI
````json POST /v3/projects/{project_id}/api-references/imports
{
"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}/api-references/imports": {
"post": {
"tags": [
"ApiReferences"
],
"summary": "Import an API reference.",
"description": "Imports an API specification (OpenAPI/Swagger) into the specified workspace. Supply the spec through\r\nexactly ONE of three sources:\r\n
\r\nThe workspace is validated before the import begins; a `422` is returned if the workspace does not exist\r\nor is not enabled for API documentation. Requires the `UpdateArticles` permission.\r\n\nThe import runs in the background. This endpoint ACCEPTS the work and returns\r\n`202 Accepted` immediately — the fetch, parse and article generation all happen afterwards. The\r\nresponse body carries `operation_id` and `operation_status: \"queued\"`, and the\r\n`Operation-Location` response header holds the URL to poll. Track it with\r\n`GET /v3/projects/{projectId}/operations/{operationId}` until its status is terminal; the import's\r\nown outcome — including any parser errors and warnings — arrives in that operation's `result`.\r\nThere is no longer a `200` on this action, and specification problems that used to come back as a\r\n`422` now surface through the operation instead.\r\n\nOne job per project. A project may have only one import or resync open at a time, because the\r\ncommit path is not transactional. A second, different request while one is open is refused with\r\n`409 Conflict` naming the operation to poll. Re-submitting an IDENTICAL request is not a conflict —\r\nit returns the operation already running rather than starting a second one.\r\n\nPolling needs a different permission from starting. This action requires\r\n`articles.update`, a CONTENT role permission, while\r\n`GET /v3/projects/{projectId}/operations/{operationId}` requires `projectSettings.view`, a\r\nPORTAL role permission. They are separate families and neither implies the other, so a token that can\r\nstart an import may receive an `Operation-Location` it cannot read. Grant both to any integration\r\nthat imports and then polls.\r\n\n\r\nThis is a property of the shared operations endpoint rather than of this action:\r\n`POST /v3/projects/{projectId}/categories/{categoryId}/archive` has the same split\r\n(`categories.delete` to start, `projectSettings.view` to poll) and predates this route.\r\n",
"operationId": "importApiReferenceJob",
"parameters": [
{
"$ref": "#/components/parameters/ProjectId"
}
],
"requestBody": {
"description": "Import configuration, from an `application/json` body or a `multipart/form-data` form.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportApiReferenceRequest"
},
"example": "
Your Zendesk account is connected to Document360. You can close this window.
", "examples": { "Import from URL": { "summary": "Imports an OpenAPI spec hosted at a public URL into the target workspace and publishes the generated articles.", "value": { "url": "https://api.example.com/openapi.json", "content": null, "workspace_id": "1c2d3e4f-5a6b-7c8d-9e0f-a1b2c3d4e5f6", "publish_articles": true, "user_id": null, "force_import": false } }, "Import from raw content (deprecated — prefer a multipart 'file' upload)": { "summary": "Imports an OpenAPI spec supplied inline as raw content (JSON or YAML). Provide exactly one of url, content, or file. Deprecated: prefer uploading the spec as 'file' via multipart/form-data.", "value": { "url": null, "content": "openapi: \"3.0.0\"\ninfo:\n title: \"Sample API\"\n version: \"1.0.0\"\npaths:\n /health:\n get:\n summary: Health check\n responses:\n \"200\":\n description: OK", "workspace_id": "1c2d3e4f-5a6b-7c8d-9e0f-a1b2c3d4e5f6", "publish_articles": false, "user_id": null, "force_import": false } } } }, "multipart/form-data": { "schema": { "required": [ "workspace_id" ], "type": "object", "properties": { "url": { "type": "string", "description": "URL to fetch the API specification from. Provide exactly one of `url`, `content`, or `file`.", "format": "uri", "nullable": true, "example": "https://api.example.com/openapi.json" }, "content": { "maxLength": 5000000, "type": "string", "description": "Deprecated: use `file` (`multipart/form-data`) instead. `content` remains fully\r\n supported for backward compatibility and is not scheduled for removal.\r\n Raw OpenAPI/Swagger specification (JSON or YAML). Provide exactly one of `url`, `content`, or `file`.\r\n Bounded to keep the in-memory buffer intentional; larger specs should be imported via `file` or `url`.", "nullable": true, "example": "openapi: \"3.0.0\"\n info:\n title: \"Sample API\"\n version: \"1.0.0\"\n paths:\n /health:\n get:\n responses:\n \"200\":\n description: OK", "deprecated": true }, "workspace_id": { "minLength": 1, "type": "string", "description": "Unique identifier of the workspace to import the API reference into." }, "publish_articles": { "type": "boolean", "description": "Whether to automatically publish the generated API reference articles after import." }, "user_id": { "type": "string", "description": "User ID that performs the publish. Required ONLY when `publish_articles` is `true` and the\r\nrequest is authenticated with an API token (M2M), because such a credential carries no user identity\r\nand publishing must be attributed to a real user. Ignored when a user access token is used - the user\r\nis taken from the token. Not needed at all when `publish_articles` is `false`.\r\nRetrieve user IDs from `GET /v3/projects/{projectId}/users`.", "nullable": true }, "force_import": { "type": "boolean", "description": "Import the specification even when it produces validation findings. Defaults to\r\n`false` (WI 340551): the caller is told what the import would do and re-submits with\r\n`force_import: true` to go ahead.\r\n\nThis default changed. It was `true`, matching the V1/V2 `ForceImport`\r\ncontract. Under `true` a resync deleted endpoints and said nothing about it, because the\r\ndeletion report was computed only on the strict path — so on V3, where this initialised to\r\n`true`, a destructive resync was always silent.\r\n\nThe gate is broader than deletions. Under `false` the request is refused if the\r\nspec yields ANY error or warning, or if `servers` is absent, so a spec that parses\r\nperfectly can still be refused. That is the cost of the change and it is deliberate: a\r\nrefusal is recoverable in one call, a silent deletion is not.\r\n" }, "file": { "type": "string", "description": "The specification file to upload. Send this field on a multipart/form-data request.", "format": "binary" } }, "description": "Request to import an API reference specification (OpenAPI/Swagger) into the project." } } } }, "responses": { "202": { "description": "Import accepted and queued; poll the operation for the outcome.", "headers": { "Operation-Location": { "description": "URL to poll for the status of the asynchronous operation.", "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/ImportApiReferenceResponseApiResponse" }, "examples": { "Import accepted": { "summary": "The API reference import was accepted and queued. Poll the operation at the Operation-Location header, or at GET /v3/projects/{projectId}/operations/{operation_id}, until its status is terminal.", "value": { "data": { "id": null, "status": null, "operation_id": "4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9a", "operation_status": "queued" }, "success": true, "request_id": "req_abc123def456", "errors": null, "warnings": null } } } } } }, "400": { "description": "The specification is larger than the project's size limit (`error_code``ApiSpecTooLarge`), or the project is at its API reference limit.", "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 } } } } } }, "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 } } } } } }, "409": { "description": "This project already has a different import or resync in progress.", "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": { "Resource conflict": { "summary": "The request conflicts with the current state of the resource.", "value": { "type": "https://apidocs.document360.io/apidocs/errors/conflict", "title": "Conflict.", "status": 409, "detail": "The resource is in a state that conflicts with this operation.", "instance": null, "trace_id": "req_abc123def456", "errors": [ { "code": "CONFLICT", "message": "A draft version already exists for this resource.", "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 } } } } } }, "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": { "ImportApiReferenceRequest": { "required": [ "workspace_id" ], "type": "object", "properties": { "url": { "type": "string", "description": "URL to fetch the API specification from. Provide exactly one of `url`, `content`, or `file`.", "format": "uri", "nullable": true, "example": "https://api.example.com/openapi.json" }, "content": { "maxLength": 5000000, "type": "string", "description": "Deprecated: use `file` (`multipart/form-data`) instead. `content` remains fully\r\n supported for backward compatibility and is not scheduled for removal.\r\n Raw OpenAPI/Swagger specification (JSON or YAML). Provide exactly one of `url`, `content`, or `file`.\r\n Bounded to keep the in-memory buffer intentional; larger specs should be imported via `file` or `url`.", "nullable": true, "example": "openapi: \"3.0.0\"\n info:\n title: \"Sample API\"\n version: \"1.0.0\"\n paths:\n /health:\n get:\n responses:\n \"200\":\n description: OK", "deprecated": true }, "workspace_id": { "minLength": 1, "type": "string", "description": "Unique identifier of the workspace to import the API reference into." }, "publish_articles": { "type": "boolean", "description": "Whether to automatically publish the generated API reference articles after import." }, "user_id": { "type": "string", "description": "User ID that performs the publish. Required ONLY when `publish_articles` is `true` and the\r\nrequest is authenticated with an API token (M2M), because such a credential carries no user identity\r\nand publishing must be attributed to a real user. Ignored when a user access token is used - the user\r\nis taken from the token. Not needed at all when `publish_articles` is `false`.\r\nRetrieve user IDs from `GET /v3/projects/{projectId}/users`.", "nullable": true }, "force_import": { "type": "boolean", "description": "Import the specification even when it produces validation findings. Defaults to\r\n`false` (WI 340551): the caller is told what the import would do and re-submits with\r\n`force_import: true` to go ahead.\r\n\nThis default changed. It was `true`, matching the V1/V2 `ForceImport`\r\ncontract. Under `true` a resync deleted endpoints and said nothing about it, because the\r\ndeletion report was computed only on the strict path — so on V3, where this initialised to\r\n`true`, a destructive resync was always silent.\r\n\nThe gate is broader than deletions. Under `false` the request is refused if the\r\nspec yields ANY error or warning, or if `servers` is absent, so a spec that parses\r\nperfectly can still be refused. That is the cost of the change and it is deliberate: a\r\nrefusal is recoverable in one call, a silent deletion is not.\r\n" } }, "additionalProperties": false, "description": "Request to import an API reference specification (OpenAPI/Swagger) into the project." }, "ImportApiReferenceResponseApiResponse": { "required": [ "data", "request_id", "success" ], "type": "object", "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/ImportApiReferenceResponse" } ], "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" }, "ImportApiReferenceResponse": { "required": [ "id", "status" ], "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier of the import task. Use this to poll for import status.", "format": "uuid", "nullable": true, "readOnly": true, "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "status": { "allOf": [ { "$ref": "#/components/schemas/ImportApiReferenceStatus" } ], "description": "Terminal status of the import operation.\r\n \r\n\nNull while the operation is still running. Import and resync are accepted with\r\n`202` and processed in the background (WI 340551), so there is no outcome to report at\r\nthat point — read Document360.Customer.API.Models.V3.ImportApiReferenceResponse.OperationStatus instead, and poll the\r\n`Operation-Location` header for the final result. Nullable on purpose: a\r\nnon-nullable enum would default to `Success` and report an outcome that has not\r\nhappened.\r\n", "nullable": true }, "operation_id": { "type": "string", "description": "Id of the background operation processing this request (WI 340551). Poll it at\r\n`GET /v3/projects/{projectId}/operations/{operationId}`, or follow the\r\n`Operation-Location` response header.", "nullable": true }, "operation_status": { "allOf": [ { "$ref": "#/components/schemas/OperationStatus" } ], "description": "Lifecycle status of that operation, in the same vocabulary the operations endpoint uses —\r\nso the value here is the value you will keep seeing while polling.", "nullable": true }, "deleted_endpoints": { "type": "array", "items": { "$ref": "#/components/schemas/DeletedEndpointResponse" }, "description": "Endpoints that existed in the previous specification, are absent from this one, and whose\r\ngenerated articles were therefore removed.\r\n \r\n\n\r\nPopulated on a RESYNC only — a first import has nothing to delete. Omitted when nothing was\r\nremoved, so a caller can treat its presence as the signal.\r\n\n\r\nAdded by WI 340551. Before it, a resync run with `force_import=true` — which was this\r\nAPI's default until the same work item changed it to `false` — removed endpoint\r\narticles and reported nothing about it. Under the current `false` default the\r\ndestructive run is refused instead, and this field reports what it WOULD remove. When you\r\nre-submit with `force_import: true` to confirm, this field is a report of what\r\nhappened, NOT a prompt: by the time you read it the deletion has already been applied.\r\nRecoverability depends on the project's recycle-bin entitlement.\r\n", "nullable": true } }, "additionalProperties": false, "description": "Response returned after initiating an API reference import." }, "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." }, "ImportApiReferenceStatus": { "enum": [ "success", "failed" ], "type": "string", "description": "Terminal status of an API reference import. Mirrors the synchronous result of the\r\nimport-spec business service.", "x-enumNames": [ "Success", "Failed" ], "x-enum-varnames": [ "Success", "Failed" ], "x-ms-enum": { "name": "ImportApiReferenceStatus", "modelAsString": true } }, "OperationStatus": { "enum": [ "queued", "running", "succeeded", "partiallySucceeded", "failed", "cancelled" ], "type": "string", "description": "Lifecycle status of an asynchronous operation. `queued` and `running` are transient —\r\nkeep polling. `succeeded`, `partiallySucceeded`, `failed`, and `cancelled`\r\nare terminal.", "x-enumNames": [ "Queued", "Running", "Succeeded", "PartiallySucceeded", "Failed", "Cancelled" ], "x-enum-varnames": [ "Queued", "Running", "Succeeded", "PartiallySucceeded", "Failed", "Cancelled" ], "x-ms-enum": { "name": "OperationStatus", "modelAsString": true } }, "DeletedEndpointResponse": { "required": [ "title" ], "type": "object", "properties": { "method": { "type": "string", "description": "HTTP method of the removed endpoint, e.g. `get`.", "nullable": true }, "path": { "type": "string", "description": "Path template of the removed endpoint, e.g. `/v2/articles/{articleId}`.", "nullable": true }, "title": { "type": "string", "description": "Title of the generated article that was removed.", "nullable": true } }, "additionalProperties": false, "description": "An endpoint removed from an API reference because it no longer appears in the specification." } }, "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