> ## 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. # List article feedback > Returns reader feedback left on the articles and page categories of one workspace and language, newest first by default. Each record carries the reader's comment, the status, the assignee and the article it was left on, plus `discussion_count`: read the team discussion from `GET /{feedback_id}/discussions` when it is above 0. The reader's name and email are not returned by any endpoint. **Scope:** `workspace_id` and `lang_code` are both required; one call lists one workspace and language. **Dates:** `start_date` / `end_date` bound when the feedback was submitted (both inclusive, either may be omitted). **Sorting:** by submission time (`sort_by=created_at`, the only sortable field), newest first unless `sort_order=asc`; ties are broken by id, so the order is stable. **Paging:** pass `next_cursor` back as `cursor` to fetch the next page, with the same filters and sort order. The cursor is stable while feedback is being added, so prefer it over `page` for a sync. When both are sent, `cursor` wins and `page` is ignored. `page` reaches the first 10,000 records; use `cursor` beyond that. **Content access:** a caller limited to certain categories or articles sees only feedback on those, and `total_count` counts only those. Requires the `ViewFeedbackManager` permission, and a plan that includes the Feedback Manager. ## OpenAPI ````json GET /v3/projects/{project_id}/feedback/articles { "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}/feedback/articles": { "get": { "tags": [ "FeedbackManagement" ], "summary": "List article feedback.", "description": "Returns reader feedback left on the articles and page categories of one workspace and language, newest\r\nfirst by default. Each record carries the reader's comment, the status, the assignee and the article it was\r\nleft on, plus `discussion_count`: read the team discussion from `GET /{feedback_id}/discussions` when it\r\nis above 0. The reader's name and email are not returned by any endpoint.\r\n \r\n**Scope:** `workspace_id` and `lang_code` are both required; one call lists one workspace and\r\nlanguage.\r\n \r\n**Dates:** `start_date` / `end_date` bound when the feedback was submitted (both inclusive, either\r\nmay be omitted).\r\n \r\n**Sorting:** by submission time (`sort_by=created_at`, the only sortable field), newest first unless\r\n`sort_order=asc`; ties are broken by id, so the order is stable.\r\n \r\n**Paging:** pass `next_cursor` back as `cursor` to fetch the next page, with the same filters and\r\nsort order. The cursor is stable while feedback is being added, so prefer it over `page` for a sync.\r\nWhen both are sent, `cursor` wins and `page` is ignored. `page` reaches the first 10,000\r\nrecords; use `cursor` beyond that.\r\n \r\n**Content access:** a caller limited to certain categories or articles sees only feedback on those, and\r\n`total_count` counts only those.\r\n \r\nRequires the `ViewFeedbackManager` permission, and a plan that includes the Feedback Manager.", "operationId": "listArticleFeedback", "parameters": [ { "$ref": "#/components/parameters/ProjectId" }, { "name": "workspace_id", "in": "query", "description": "Identifier of the workspace to list feedback for.", "required": true, "schema": { "type": "string", "format": "uuid", "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, { "name": "lang_code", "in": "query", "description": "Language code to list feedback for; must be a language of the workspace.", "required": true, "schema": { "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$", "type": "string", "example": "en" }, "example": "en" }, { "name": "article_id", "in": "query", "description": "Only feedback on this article, or on this page category. Optional.", "schema": { "type": "string", "format": "uuid", "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, "example": "9a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d" }, { "name": "status", "in": "query", "description": "Only feedback in these statuses. Repeat the key for several values (`status=open&status=inProgress`). Values: `open`, `planned`, `inProgress`, `complete`, `closed`, matched case-insensitively.", "schema": { "type": "array", "items": { "enum": [ "open", "planned", "inProgress", "complete", "closed" ], "type": "string", "example": "open" }, "example": [ "open" ] }, "example": [ "open" ] }, { "name": "feedback_type", "in": "query", "description": "Only `like` (liked) or `dislike` (disliked) feedback. Optional.", "schema": { "enum": [ "like", "dislike" ], "type": "string", "example": "like" }, "example": "like" }, { "name": "article_type", "in": "query", "description": "Filter by type: `all`, `article`, or `page_category`. Defaults to `all`; any other value is refused with `422`.", "schema": { "enum": [ "all", "article", "page_category" ], "type": "string", "default": "all", "example": "all" }, "example": "all" }, { "name": "start_date", "in": "query", "description": "Only feedback submitted at or after this time (ISO 8601; a value without an offset is UTC). Optional.", "schema": { "type": "string", "format": "date-time", "example": "2025-07-01T00:00:00Z" }, "example": "2025-07-01T00:00:00Z" }, { "name": "end_date", "in": "query", "description": "Only feedback submitted at or before this time (ISO 8601; a value without an offset is UTC). A date without a time (`2026-10-26`) includes that whole day, in UTC. Optional; must not be before `start_date`.", "schema": { "type": "string", "format": "date-time", "example": "2025-08-15T23:59:59Z" }, "example": "2025-08-15T23:59:59Z" }, { "name": "sort_by", "in": "query", "description": "Field to sort by. Only `created_at` (submission time) is supported. Defaults to `created_at`.", "schema": { "enum": [ "created_at" ], "type": "string", "default": "created_at", "example": "created_at" }, "example": "created_at" }, { "name": "sort_order", "in": "query", "description": "`desc` (newest first, the default) or `asc` (oldest first).", "schema": { "enum": [ "asc", "desc" ], "type": "string", "default": "desc", "example": "desc" }, "example": "desc" }, { "$ref": "#/components/parameters/Page" }, { "$ref": "#/components/parameters/PageSize" }, { "name": "cursor", "in": "query", "description": "The `next_cursor` of the previous page, unchanged. An edited or shortened cursor is refused with `422`.", "schema": { "type": "string", "example": "NjM5MjczMjY0MDAwMDAwMDAwfDc3YTJiM2M0LWQ1ZTYtNGY3MC04MTkyLWEzYjRjNWQ2ZTdmOHw3ODBhNWQwZjlmYmE" }, "example": "NjM5MjczMjY0MDAwMDAwMDAwfDc3YTJiM2M0LWQ1ZTYtNGY3MC04MTkyLWEzYjRjNWQ2ZTdmOHw3ODBhNWQwZjlmYmE" }, { "name": "include_total_count", "in": "query", "description": "Whether to return the total number of matching records in `pagination.total_count`. Each request with it runs a separate count, so send it on the first page only and reuse the value while paging.", "schema": { "type": "boolean", "default": false, "example": false }, "example": false } ], "responses": { "200": { "description": "Feedback retrieved successfully.", "headers": { "X-RateLimit-Limit": { "description": "The maximum number of requests allowed in the current time window for this request's bucket. Read requests (GET/HEAD) and write requests (POST/PUT/PATCH/DELETE) have independent limits, applied per caller (API key or user) per project.", "schema": { "type": "integer", "format": "int32" } }, "X-RateLimit-Remaining": { "description": "The number of requests remaining in the current time window. When this reaches 0, subsequent requests receive a 429 response.", "schema": { "type": "integer", "format": "int32" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FeedbackSummaryResponsePaginatedResponse" }, "examples": { "Feedback page with more to fetch": { "summary": "A disliked article being worked on and a liked one nobody has picked up. A like always carries the `comments` reason. Pass `next_cursor` back as `cursor` for the next page.", "value": { "data": [ { "feedback_id": "66f1a2b3-c4d5-4e6f-a7b8-c9d0e1f2a3b4", "article_id": "9a3b4c5d-6e7f-4a9b-8c1d-2e3f4a5b6c7d", "article_title": "Getting started", "article_url": "https://docs.example.com/docs/en/getting-started", "workspace_id": "4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a", "lang_code": "en", "feedback_type": "dislike", "is_page_category": false, "feedback_reasons": [ "inaccurate_or_irrelevant", "missing_or_broken_link" ], "reader_comment": "The setup steps are out of date.", "status": "inProgress", "assignee_id": "2b1a0f9e-8d7c-4b6a-9f5e-4d3c2b1a0f9e", "created_at": "2026-10-10T08:15:00Z", "updated_at": "2026-10-12T10:30:00Z", "discussion_count": 2 }, { "feedback_id": "77a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8", "article_id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e", "article_title": "Configuring SSO", "article_url": "https://docs.example.com/docs/en/configuring-sso", "workspace_id": "4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a", "lang_code": "en", "feedback_type": "like", "is_page_category": false, "feedback_reasons": [ "comments" ], "reader_comment": "Very clear, thanks!", "status": "open", "assignee_id": null, "created_at": "2026-10-11T14:40:00Z", "updated_at": "2026-10-11T14:40:00Z", "discussion_count": 0 } ], "pagination": { "page": 1, "page_size": 2, "total_count": null, "has_more": true, "next_cursor": "NjM5MjczMjY0MDAwMDAwMDAwfDc3YTJiM2M0LWQ1ZTYtNGY3MC04MTkyLWEzYjRjNWQ2ZTdmOHw3ODBhNWQwZjlmYmE" }, "success": true, "request_id": "0HN7A2K3L4M5N:00000001", "errors": null, "warnings": null } }, "No matching feedback": { "summary": "Nothing matches the filters. With `include_total_count=true`, `total_count` is 0.", "value": { "data": [], "pagination": { "page": 1, "page_size": 25, "total_count": 0, "has_more": false, "next_cursor": null }, "success": true, "request_id": "0HN7A2K3L4M5N:00000002", "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": "The caller lacks the `ViewFeedbackManager` permission or access to the workspace or language, or the plan does not include the Feedback Manager.", "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": "Workspace 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 (missing scope, a `workspace_id` or `article_id` that is not an id, unknown language, invalid status, feedback type, article type, date range, sort, cursor or paging, or a page beyond the first 10,000 records).", "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" } }, "Page": { "name": "page", "in": "query", "description": "Page number (1-based). Defaults to 1.", "schema": { "minimum": 1, "type": "integer", "format": "int32", "default": 1 } }, "PageSize": { "name": "page_size", "in": "query", "description": "Number of results per page. Defaults to 25. Maximum 100.", "schema": { "maximum": 100, "minimum": 1, "type": "integer", "format": "int32", "default": 25 } } }, "schemas": { "FeedbackSummaryResponsePaginatedResponse": { "required": [ "data", "pagination", "request_id", "success" ], "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/FeedbackSummaryResponse" }, "description": "List of items for the current page." }, "pagination": { "allOf": [ { "$ref": "#/components/schemas/PaginationInfo" } ], "description": "Pagination metadata.", "readOnly": true }, "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": "Paginated API response containing a list of items." }, "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" }, "FeedbackSummaryResponse": { "required": [ "created_at", "discussion_count", "is_page_category", "status", "updated_at" ], "type": "object", "properties": { "feedback_id": { "type": "string", "description": "Unique identifier of the feedback record.", "nullable": true, "readOnly": true, "example": "66f1a2b3-c4d5-4e6f-a7b8-c9d0e1f2a3b4" }, "article_id": { "type": "string", "description": "Identifier of the article the feedback was left on. For page-category feedback\r\n(`is_page_category` = `true`) this is the page category's id. Null only for a legacy record\r\nthat never stored one.", "nullable": true, "readOnly": true, "example": "9a3b4c5d-6e7f-4a9b-8c1d-2e3f4a5b6c7d" }, "article_title": { "type": "string", "description": "Title of the article or page category in the feedback's language. Null when the article has been\r\ndeleted or has no published version in that language.", "nullable": true, "readOnly": true, "example": "Getting started" }, "article_url": { "type": "string", "description": "Public knowledge base URL of the article or page category. Null when it has been deleted or has no published version.", "nullable": true, "readOnly": true, "example": "https://docs.example.com/docs/en/getting-started" }, "workspace_id": { "type": "string", "description": "Identifier of the workspace the feedback belongs to.", "nullable": true, "readOnly": true, "example": "4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a" }, "lang_code": { "type": "string", "description": "Language code of the article version the reader left feedback on.", "nullable": true, "readOnly": true, "example": "en" }, "feedback_type": { "enum": [ "like", "dislike" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/V3FeedbackType" } ], "description": "Whether the reader liked (`like`) or disliked (`dislike`) the article.", "readOnly": true, "example": "dislike", "x-enumNames": [ "Like", "Dislike" ], "x-enum-varnames": [ "Like", "Dislike" ], "x-ms-enum": { "name": "V3FeedbackType", "modelAsString": true } }, "is_page_category": { "type": "boolean", "description": "Whether the feedback was left on a page category (`true`) rather than an article (`false`).\r\nFor page-category feedback the `article_*` fields describe the page category.", "readOnly": true, "example": false }, "feedback_reasons": { "type": "array", "items": { "type": "string" }, "description": "The reasons the reader picked. Zero or more of `need_more_information`,\r\n`difficult_to_understand`, `inaccurate_or_irrelevant`, `missing_or_broken_link`,\r\n`others` and `comments`. Empty when the reader picked none.", "nullable": true, "readOnly": true, "example": [ "inaccurate_or_irrelevant", "missing_or_broken_link" ] }, "reader_comment": { "type": "string", "description": "The comment the reader wrote with the feedback. Team discussions are separate; read them from\r\n`GET /{feedback_id}/discussions`.", "nullable": true, "readOnly": true, "example": "The setup steps are out of date." }, "status": { "enum": [ "open", "planned", "inProgress", "complete", "closed" ], "type": "string", "allOf": [ { "$ref": "#/components/schemas/V3FeedbackStatus" } ], "description": "Current lifecycle status of the feedback.", "readOnly": true, "example": "inProgress", "x-enumNames": [ "Open", "Planned", "InProgress", "Complete", "Closed" ], "x-enum-varnames": [ "Open", "Planned", "InProgress", "Complete", "Closed" ], "x-ms-enum": { "name": "V3FeedbackStatus", "modelAsString": true } }, "assignee_id": { "type": "string", "description": "Identifier of the team member the feedback is assigned to. Null when unassigned.", "nullable": true, "readOnly": true, "example": "2b1a0f9e-8d7c-4b6a-9f5e-4d3c2b1a0f9e" }, "created_at": { "type": "string", "description": "When the reader submitted the feedback (UTC).", "format": "date-time", "readOnly": true, "example": "2026-10-10T08:15:00Z" }, "updated_at": { "type": "string", "description": "When the feedback last changed (UTC): the latest status change, assignment, close, note or team\r\ndiscussion, or `created_at` when nothing has changed since it was submitted.", "format": "date-time", "readOnly": true, "example": "2026-10-12T11:02:00Z" }, "discussion_count": { "type": "integer", "description": "Number of team discussion entries on the feedback, including the notes added in the Portal. Call `GET /{feedback_id}/discussions` only when it is above 0.", "format": "int32", "readOnly": true, "example": 2 } }, "additionalProperties": false, "description": "One reader feedback record on an article or a page category, as returned by the list endpoint." }, "PaginationInfo": { "required": [ "has_more", "page", "page_size" ], "type": "object", "properties": { "page": { "type": "integer", "description": "Current page number (1-based). Returns 0 when using cursor-based pagination.", "format": "int32" }, "page_size": { "type": "integer", "description": "Number of items per page.", "format": "int32" }, "total_count": { "type": "integer", "description": "Total number of items across all pages. Only populated when `include_total_count=true` is specified in the request.", "format": "int64", "nullable": true }, "has_more": { "type": "boolean", "description": "Whether additional pages are available." }, "next_cursor": { "type": "string", "description": "Opaque cursor to retrieve the next page of results. Pass this value as the `cursor` query parameter. Null when there are no more pages.", "nullable": true } }, "additionalProperties": false, "description": "Metadata describing the current pagination state. Supports both offset-based\r\nand cursor-based pagination." }, "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." }, "V3FeedbackType": { "enum": [ "like", "dislike" ], "type": "string", "description": "Whether the reader liked (`like`) or disliked (`dislike`) the article, as on the knowledge base's\r\nlike / dislike buttons.", "x-enumNames": [ "Like", "Dislike" ], "x-enum-varnames": [ "Like", "Dislike" ], "x-ms-enum": { "name": "V3FeedbackType", "modelAsString": true } }, "V3FeedbackStatus": { "enum": [ "open", "planned", "inProgress", "complete", "closed" ], "type": "string", "description": "Lifecycle status of a feedback record. A `closed` feedback cannot be changed through the API; it can be\r\nreopened only in the Document360 portal.", "x-enumNames": [ "Open", "Planned", "InProgress", "Complete", "Closed" ], "x-enum-varnames": [ "Open", "Planned", "InProgress", "Complete", "Closed" ], "x-ms-enum": { "name": "V3FeedbackStatus", "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" } } } } ````