> For the complete documentation index, see [llms.txt](https://boundaryai.gitbook.io/boundaryai-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://boundaryai.gitbook.io/boundaryai-docs/api-and-webhooks/classic-vocabulary-aliases.md).

# Classic Vocabulary (aliases)

The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field.

## Push content rows to a question

> Append free-text or typed content to one question on a survey via the customer's Input API key. Idempotent when an \`Idempotency-Key\` header is provided — duplicate keys with the same payload replay the original response within a 24h window.\
> \
> \*\*Requires permission:\*\* \`push\` or \`create\` or \`all\`.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"schemas":{"ContentPushRequest":{"properties":{"survey_series_id":{"description":"Target survey series id","title":"Survey Series Id","type":"string"},"survey_id":{"description":"Target survey id within the series","title":"Survey Id","type":"string"},"question":{"$ref":"#/components/schemas/QuestionContentPush"},"source_reference":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"description":"Free-form identifier the customer attaches to the push for their own downstream tracking (e.g. an upstream ticket id).","title":"Source Reference"}},"required":["survey_series_id","survey_id","question"],"title":"ContentPushRequest","type":"object"},"QuestionContentPush":{"properties":{"question_id":{"description":"Target question id (integer as string)","title":"Question Id","type":"string"},"content":{"description":"Feedback entries — bare strings or structured items with ingest metadata","items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/ContentItem"}]},"minItems":1,"title":"Content","type":"array"}},"required":["question_id","content"],"title":"QuestionContentPush","type":"object"},"ContentItem":{"description":"Structured content entry with ingest metadata.\n\nContent arrays accept bare strings (the original shape) or these\nobjects. ``external_id`` is the customer-side id used for\nupsert-dedup on retries; ``occurred_at`` back-dates the item for\nperiod bucketing; the rest is stored as structured context.","properties":{"text":{"description":"The feedback text","minLength":1,"title":"Text","type":"string"},"external_id":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"description":"Customer-side id of this item (ticket id, review id…). Items whose (question, external_id) already exist are skipped, so retries never create duplicates.","title":"External Id"},"customer_id":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"title":"Customer Id"},"channel":{"anyOf":[{"maxLength":100,"type":"string"},{"type":"null"}],"default":null,"title":"Channel"},"language":{"anyOf":[{"maxLength":35,"type":"string"},{"type":"null"}],"default":null,"description":"BCP-47-ish language tag","title":"Language"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"description":"Numeric score attached to the item (e.g. review stars)","title":"Rating"},"occurred_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"ISO-8601 timestamp of when the feedback happened (defaults to push time)","title":"Occurred At"}},"required":["text"],"title":"ContentItem","type":"object"},"ContentPushResponse":{"description":"Success body of POST /api/input/content/push.","properties":{"status":{"const":"success","description":"Always 'success' on this path","title":"Status","type":"string"},"survey_series_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Series the resolved survey belongs to","title":"Survey Series Id"},"survey_id":{"description":"Resolved survey id where content landed","title":"Survey Id","type":"integer"},"question_id":{"description":"Resolved question id where content landed","title":"Question Id","type":"integer"},"inserted":{"description":"Number of content rows inserted","minimum":0,"title":"Inserted","type":"integer"},"rows_accepted":{"anyOf":[{"minimum":0,"type":"integer"},{"type":"null"}],"default":null,"description":"Rows admitted for processing (inserted + skipped duplicates)","title":"Rows Accepted"},"aps_deducted":{"description":"Legacy alias of rows_accepted. Under token-based billing no APS is pre-deducted at push time — actual credit usage is metered during analysis, and test-environment keys are never charged. A fully-deduplicated retry still reports its admitted row count here.","minimum":0,"title":"Aps Deducted","type":"integer"},"skipped_duplicates":{"anyOf":[{"minimum":0,"type":"integer"},{"type":"null"}],"default":null,"description":"Rows skipped because their (question, external_id) already existed","title":"Skipped Duplicates"},"duplicate_external_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"default":null,"description":"External ids of the skipped rows (first 100)","title":"Duplicate External Ids"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Human-readable confirmation","title":"Message"}},"required":["status","survey_id","question_id","inserted","aps_deducted"],"title":"ContentPushResponse","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/content/push":{"post":{"summary":"Push content rows to a question","description":"Append free-text or typed content to one question on a survey via the customer's Input API key. Idempotent when an `Idempotency-Key` header is provided — duplicate keys with the same payload replay the original response within a 24h window.\n\n**Requires permission:** `push` or `create` or `all`.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_post_content_push","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Opaque client-supplied key for safe retries (24h window)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentPushRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ContentPushResponse"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"Payment Required — insufficient credits / APS","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict (idempotency key reuse or resource conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Submit a bulk content push (async)

> Queue a multi-question content batch for async processing. Returns 202 with a \`task\_id\` and \`status\_url\` — poll the status endpoint for the final result. Idempotent via \`Idempotency-Key\` header (24h window).\
> \
> \*\*Requires permission:\*\* \`push\` or \`create\` or \`all\`.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"schemas":{"BulkContentPushRequest":{"properties":{"survey_series_id":{"title":"Survey Series Id","type":"string"},"survey_id":{"title":"Survey Id","type":"string"},"questions":{"description":"Up to 100 question-content pairs in a single batch","items":{"$ref":"#/components/schemas/BulkQuestionContentPush"},"maxItems":100,"minItems":1,"title":"Questions","type":"array"},"source_reference":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"title":"Source Reference"}},"required":["survey_series_id","survey_id","questions"],"title":"BulkContentPushRequest","type":"object"},"BulkQuestionContentPush":{"properties":{"question_id":{"description":"Target question id","title":"Question Id","type":"string"},"content":{"description":"Feedback entries — bare strings or structured items","items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/ContentItem"}]},"minItems":1,"title":"Content","type":"array"}},"required":["question_id","content"],"title":"BulkQuestionContentPush","type":"object"},"ContentItem":{"description":"Structured content entry with ingest metadata.\n\nContent arrays accept bare strings (the original shape) or these\nobjects. ``external_id`` is the customer-side id used for\nupsert-dedup on retries; ``occurred_at`` back-dates the item for\nperiod bucketing; the rest is stored as structured context.","properties":{"text":{"description":"The feedback text","minLength":1,"title":"Text","type":"string"},"external_id":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"description":"Customer-side id of this item (ticket id, review id…). Items whose (question, external_id) already exist are skipped, so retries never create duplicates.","title":"External Id"},"customer_id":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"title":"Customer Id"},"channel":{"anyOf":[{"maxLength":100,"type":"string"},{"type":"null"}],"default":null,"title":"Channel"},"language":{"anyOf":[{"maxLength":35,"type":"string"},{"type":"null"}],"default":null,"description":"BCP-47-ish language tag","title":"Language"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"description":"Numeric score attached to the item (e.g. review stars)","title":"Rating"},"occurred_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"ISO-8601 timestamp of when the feedback happened (defaults to push time)","title":"Occurred At"}},"required":["text"],"title":"ContentItem","type":"object"},"BulkPushAccepted":{"description":"202 body of POST /api/input/content/push/bulk.","properties":{"status":{"const":"accepted","title":"Status","type":"string"},"task_id":{"title":"Task Id","type":"string"},"survey_series_id":{"title":"Survey Series Id","type":"integer"},"survey_id":{"title":"Survey Id","type":"integer"},"status_url":{"title":"Status Url","type":"string"}},"required":["status","task_id","survey_series_id","survey_id","status_url"],"title":"BulkPushAccepted","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/content/push/bulk":{"post":{"summary":"Submit a bulk content push (async)","description":"Queue a multi-question content batch for async processing. Returns 202 with a `task_id` and `status_url` — poll the status endpoint for the final result. Idempotent via `Idempotency-Key` header (24h window).\n\n**Requires permission:** `push` or `create` or `all`.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_post_content_push_bulk","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Opaque retry key (24h window)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkContentPushRequest"}}}},"responses":{"202":{"description":"Accepted — task queued for async processing","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BulkPushAccepted"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Conflict (idempotency key reuse or resource conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Poll the status of a bulk content push

> Returns 202 while pending/processing, 200 on completed or failed (the discriminator is the \`status\` field, not the HTTP code, so the success-vs-recorded-failure path stays machine-readable). 404 when the task id is unknown for the calling org.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"schemas":{"BulkPushStatus":{"description":"200/202 body of GET /api/input/content/push/bulk/status/{task_id}.","properties":{"task_id":{"title":"Task Id","type":"string"},"status":{"enum":["pending","processing","completed","failed"],"title":"Status","type":"string"},"survey_series_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Survey Series Id"},"survey_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Survey Id"},"progress":{"default":0,"description":"Percent complete (0-100)","maximum":100,"minimum":0,"title":"Progress","type":"number"},"progress_message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Progress Message"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"ISO-8601 UTC","title":"Created At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"ISO-8601 UTC","title":"Completed At"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"description":"Present on status=completed: {status, survey_series_id, survey_id, total_inserted, total_rows_accepted, total_aps_deducted, questions: [{question_id, inserted, status}, ...]}","title":"Result"},"error":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"description":"Error payload, present on status=failed","title":"Error"}},"required":["task_id","status"],"title":"BulkPushStatus","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/content/push/bulk/status/{task_id}":{"get":{"summary":"Poll the status of a bulk content push","description":"Returns 202 while pending/processing, 200 on completed or failed (the discriminator is the `status` field, not the HTTP code, so the success-vs-recorded-failure path stays machine-readable). 404 when the task id is unknown for the calling org.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_get_content_push_bulk_status","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BulkPushStatus"}},"required":["data"]}}}},"202":{"description":"Accepted — task queued for async processing","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BulkPushStatus"}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create a survey inside a series

> Creates a new survey in EDITING mode with the supplied questions. The survey is not publicly accessible until \`/survey/publish\` is called. Question types are case-insensitive.\
> \
> \*\*Requires permission:\*\* \`create\` or \`all\`.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Opaque client-supplied key for safe POST retries. Repeated requests with the same key within a 24-hour window replay the original response instead of executing the mutation twice. Reusing the same key with a DIFFERENT request body returns 409 `IDEMPOTENCY_ERROR`. Backed by ``input_api_idempotency_keys`` in Postgres.","schema":{"type":"string","minLength":1,"maxLength":255}}},"schemas":{"CreateSurveyRequest":{"properties":{"survey_series_id":{"title":"Survey Series Id","type":"string"},"survey_title":{"maxLength":100,"minLength":1,"title":"Survey Title","type":"string"},"survey_description":{"default":"","maxLength":1000,"title":"Survey Description","type":"string"},"feedback_type":{"default":"survey","description":"What kind of feedback this source collects: survey | call_transcript | app_review | support_ticket | chat | email | social_media | review | other. Open vocabulary — new types may be added without a version bump.","title":"Feedback Type","type":"string"},"questions":{"items":{"$ref":"#/components/schemas/CreateSurveyQuestion"},"title":"Questions","type":"array"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"EN | FR | ES (or full names; case-insensitive)","title":"Language"}},"required":["survey_series_id","survey_title"],"title":"CreateSurveyRequest","type":"object"},"CreateSurveyQuestion":{"description":"One question in the create-survey payload. Permissive on\nquestion_type to match the existing case-insensitive parser.","properties":{"question_title":{"maxLength":500,"minLength":1,"title":"Question Title","type":"string"},"question_type":{"description":"One of DEPTH_TEXT, TEXT, MCQ, SCQ, RATING, NPS, METADATA (case-insensitive; also accepts long_answer / short_answer).","title":"Question Type","type":"string"},"question_options":{"items":{"type":"string"},"maxItems":100,"title":"Question Options","type":"array"},"is_mandatory":{"default":false,"title":"Is Mandatory","type":"boolean"},"min_value":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Min Value"},"max_value":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Max Value"},"min_label":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Min Label"},"max_label":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Max Label"},"use_star_rating":{"default":false,"title":"Use Star Rating","type":"boolean"}},"required":["question_title","question_type"],"title":"CreateSurveyQuestion","type":"object"},"CreateSurveyResponse":{"properties":{"status":{"const":"success","title":"Status","type":"string"},"survey_series_id":{"title":"Survey Series Id","type":"integer"},"survey_id":{"title":"Survey Id","type":"integer"},"feedback_type":{"default":"survey","title":"Feedback Type","type":"string"},"questions_created":{"title":"Questions Created","type":"integer"},"questions":{"items":{"$ref":"#/components/schemas/CreatedQuestion"},"title":"Questions","type":"array"},"message":{"title":"Message","type":"string"}},"required":["status","survey_series_id","survey_id","questions_created","questions","message"],"title":"CreateSurveyResponse","type":"object"},"CreatedQuestion":{"properties":{"id":{"title":"Id","type":"integer"},"title":{"title":"Title","type":"string"},"type":{"description":"The normalized question type echoed back","title":"Type","type":"string"}},"required":["id","title","type"],"title":"CreatedQuestion","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/survey/create":{"post":{"summary":"Create a survey inside a series","description":"Creates a new survey in EDITING mode with the supplied questions. The survey is not publicly accessible until `/survey/publish` is called. Question types are case-insensitive.\n\n**Requires permission:** `create` or `all`.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_post_survey_create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSurveyRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateSurveyResponse"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Publish a survey (move to ACCESSIBLE mode)

> Idempotent: publishing an already-ACCESSIBLE survey is a no-op that returns 200 with the same shape.\
> \
> \*\*Requires permission:\*\* \`create\` or \`all\`.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Opaque client-supplied key for safe POST retries. Repeated requests with the same key within a 24-hour window replay the original response instead of executing the mutation twice. Reusing the same key with a DIFFERENT request body returns 409 `IDEMPOTENCY_ERROR`. Backed by ``input_api_idempotency_keys`` in Postgres.","schema":{"type":"string","minLength":1,"maxLength":255}}},"schemas":{"PublishSurveyRequest":{"properties":{"survey_id":{"title":"Survey Id","type":"integer"},"survey_series_id":{"title":"Survey Series Id","type":"integer"}},"required":["survey_id","survey_series_id"],"title":"PublishSurveyRequest","type":"object"},"PublishSurveyResponse":{"properties":{"status":{"const":"success","title":"Status","type":"string"},"survey_id":{"title":"Survey Id","type":"integer"},"survey_series_id":{"title":"Survey Series Id","type":"integer"},"mode":{"const":"ACCESSIBLE","title":"Mode","type":"string"},"message":{"title":"Message","type":"string"}},"required":["status","survey_id","survey_series_id","mode","message"],"title":"PublishSurveyResponse","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/survey/publish":{"post":{"summary":"Publish a survey (move to ACCESSIBLE mode)","description":"Idempotent: publishing an already-ACCESSIBLE survey is a no-op that returns 200 with the same shape.\n\n**Requires permission:** `create` or `all`.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_post_survey_publish","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSurveyRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PublishSurveyResponse"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create a survey series

> Idempotent on series name within the org — if a series with the same name already exists, returns 200 with \`created: false\` and the existing series id.\
> \
> \*\*Requires permission:\*\* \`all\`.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Opaque client-supplied key for safe POST retries. Repeated requests with the same key within a 24-hour window replay the original response instead of executing the mutation twice. Reusing the same key with a DIFFERENT request body returns 409 `IDEMPOTENCY_ERROR`. Backed by ``input_api_idempotency_keys`` in Postgres.","schema":{"type":"string","minLength":1,"maxLength":255}}},"schemas":{"CreateSurveySeriesRequest":{"properties":{"name":{"description":"Series display name","maxLength":255,"minLength":2,"title":"Name","type":"string"}},"required":["name"],"title":"CreateSurveySeriesRequest","type":"object"},"CreateSurveySeriesResponse":{"description":"200 (already exists) / 201 (created) body.","properties":{"status":{"const":"success","title":"Status","type":"string"},"created":{"description":"False when the series already existed","title":"Created","type":"boolean"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Message"},"survey_series":{"$ref":"#/components/schemas/CreatedSeriesMinimal"}},"required":["status","created","survey_series"],"title":"CreateSurveySeriesResponse","type":"object"},"CreatedSeriesMinimal":{"properties":{"id":{"title":"Id","type":"integer"},"name":{"title":"Name","type":"string"}},"required":["id","name"],"title":"CreatedSeriesMinimal","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/survey_series/create":{"post":{"summary":"Create a survey series","description":"Idempotent on series name within the org — if a series with the same name already exists, returns 200 with `created: false` and the existing series id.\n\n**Requires permission:** `all`.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_post_survey_series_create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSurveySeriesRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateSurveySeriesResponse"}},"required":["data"]}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateSurveySeriesResponse"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List surveys accessible to the API key's organization

> API-key-authenticated variant — exposes extended fields (\`accepts\_content\`, \`content\_format\`, \`mode\`) that the Clerk variant omits, for CLI / integration usage.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"schemas":{"SurveysListResponse":{"description":"Body of GET /api/input/surveys and /api/input/surveys/list.\n\nThe `org_id` field is present on the API-key endpoint and omitted on\nthe Clerk endpoint — declare it Optional.","properties":{"survey_series":{"items":{"$ref":"#/components/schemas/SeriesListing"},"title":"Survey Series","type":"array"},"pagination":{"anyOf":[{"$ref":"#/components/schemas/ListPagination"},{"type":"null"}],"default":null},"org_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Org Id"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Message"}},"title":"SurveysListResponse","type":"object"},"SeriesListing":{"properties":{"id":{"title":"Id","type":"integer"},"name":{"title":"Name","type":"string"},"surveys":{"items":{"$ref":"#/components/schemas/SurveyListing"},"title":"Surveys","type":"array"}},"required":["id","name"],"title":"SeriesListing","type":"object"},"SurveyListing":{"properties":{"id":{"title":"Id","type":"integer"},"title":{"title":"Title","type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Mode"},"feedback_type":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"What kind of feedback this source collects (survey | call_transcript | …)","title":"Feedback Type"},"questions":{"items":{"$ref":"#/components/schemas/QuestionListing"},"title":"Questions","type":"array"}},"required":["id","title"],"title":"SurveyListing","type":"object"},"QuestionListing":{"properties":{"id":{"title":"Id","type":"integer"},"title":{"title":"Title","type":"string"},"type":{"title":"Type","type":"string"},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"default":null,"title":"Options"},"accepts_content":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Accepts Content"},"content_format":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"One of text | numeric | single_choice | multi_choice","title":"Content Format"},"min":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Min"},"max":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Max"}},"required":["id","title","type"],"title":"QuestionListing","type":"object"},"ListPagination":{"description":"Series-level pagination echo on the surveys-list endpoints.\n\n``total_count`` is the number of series matching the filters, so\nclients page while ``offset + limit < total_count``.","properties":{"limit":{"title":"Limit","type":"integer"},"offset":{"title":"Offset","type":"integer"},"total_count":{"title":"Total Count","type":"integer"}},"required":["limit","offset","total_count"],"title":"ListPagination","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/surveys/list":{"get":{"summary":"List surveys accessible to the API key's organization","description":"API-key-authenticated variant — exposes extended fields (`accepts_content`, `content_format`, `mode`) that the Clerk variant omits, for CLI / integration usage.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_get_surveys_list","parameters":[{"name":"survey_series_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"survey_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Max survey series returned per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Number of series to skip (series-level pagination)."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SurveysListResponse"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Read processed analysis for a survey

> The read half of the API: pull sentiment distribution, themes and monitored flags back out once analysis has run on pushed feedback. Flags are monitored at the feedback-group level, so the \`flags\` list covers the whole group this survey belongs to. Analysis launches automatically after content lands and takes minutes to tens of minutes depending on volume and queue depth; \`analysis\_status: "none"\` means no completed run has been persisted yet (it does not distinguish queued from in-progress). Pair with the \`analysis.completed\` webhook — registered in the dashboard's Integrations Hub — instead of polling.\
> \
> \*\*Response envelope migration:\*\* during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's \`data\` property, without the \`{"data": ...}\` wrapper). The \`Deprecation\` and \`Sunset\` headers on every 2xx announce this envelope migration — they do \*\*not\*\* deprecate the endpoint itself. After the \`Sunset\` date, responses will be wrapped as \`{"data": ...}\` exactly as the schema declares.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.3.0"},"tags":[{"name":"Classic vocabulary (aliases)","description":"The same operations in the original `survey_series` / `survey` / `question` naming. Fully supported and **not** deprecated — but new integrations should prefer the feedback-group routes above, which match the product's own vocabulary. A `survey_series` is a feedback group; a `survey` is a source; a `question` is a field."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"BAI Analytics API key","description":"API key authentication: pass your key in the Authorization header as `Bearer inpk_live_...` (or `inpk_test_...` for a test-environment key). Create and manage keys in the dashboard under Integrations Hub -> API Keys. Each key carries a permission scope (push, create, send, mcp_read, or all) and a per-minute rate limit."}},"schemas":{"SurveyAnalysisReadResponse":{"description":"Processed analysis for one survey — the read half of the API.\n\n``analysis_status`` is 'available' once at least one analysis run has\nsettled; 'none' means no analysis exists yet (nothing pushed, or the\nfirst run is still in flight — subscribe to the `analysis.completed`\nwebhook instead of polling).\n\n``analysis_id`` names the source's all-time analysis run. A source whose\nfeedback group tracks trends over time is analyzed per period instead:\nit has no single run to name, so ``analysis_id`` is null while\n``analysis_status`` is 'available' and the figures aggregate every\nanalyzed period.","properties":{"survey_id":{"title":"Survey Id","type":"integer"},"survey_series_id":{"title":"Survey Series Id","type":"integer"},"feedback_type":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Feedback Type"},"analysis_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Analysis Id"},"analysis_status":{"enum":["available","none"],"title":"Analysis Status","type":"string"},"sentiment_distribution":{"$ref":"#/components/schemas/SentimentDistribution"},"themes":{"items":{"$ref":"#/components/schemas/AnalysisTheme"},"title":"Themes","type":"array"},"flags":{"items":{"$ref":"#/components/schemas/AnalysisFlag"},"title":"Flags","type":"array"}},"required":["survey_id","survey_series_id","analysis_status","sentiment_distribution"],"title":"SurveyAnalysisReadResponse","type":"object"},"SentimentDistribution":{"description":"5-tier mention-weighted sentiment distribution.","properties":{"veryPositive":{"default":0,"title":"Verypositive","type":"integer"},"positive":{"default":0,"title":"Positive","type":"integer"},"neutral":{"default":0,"title":"Neutral","type":"integer"},"negative":{"default":0,"title":"Negative","type":"integer"},"veryNegative":{"default":0,"title":"Verynegative","type":"integer"}},"title":"SentimentDistribution","type":"object"},"AnalysisTheme":{"properties":{"name":{"title":"Name","type":"string"},"mentions":{"minimum":0,"title":"Mentions","type":"integer"},"avg_sentiment":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"description":"-1..1 average sentiment across mentions","title":"Avg Sentiment"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"}},"required":["name","mentions"],"title":"AnalysisTheme","type":"object"},"AnalysisFlag":{"properties":{"name":{"title":"Name","type":"string"},"mentions":{"minimum":0,"title":"Mentions","type":"integer"},"avg_sentiment":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"title":"Avg Sentiment"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Summary"},"scope":{"const":"survey_series","default":"survey_series","description":"Flags are monitored at feedback-group level; the list covers the whole group this survey belongs to.","title":"Scope","type":"string"}},"required":["name","mentions"],"title":"AnalysisFlag","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 responses and on 503 responses from the global rate limiter. Rate-limit windows are fixed (anchored at the first request in the window): requests rejected with 429 do not extend the window, so waiting until ``X-RateLimit-Reset`` always recovers.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets. The window is fixed from its first request — 429-rejected requests do not push this forward.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/input/surveys/{survey_id}/analysis":{"get":{"summary":"Read processed analysis for a survey","description":"The read half of the API: pull sentiment distribution, themes and monitored flags back out once analysis has run on pushed feedback. Flags are monitored at the feedback-group level, so the `flags` list covers the whole group this survey belongs to. Analysis launches automatically after content lands and takes minutes to tens of minutes depending on volume and queue depth; `analysis_status: \"none\"` means no completed run has been persisted yet (it does not distinguish queued from in-progress). Pair with the `analysis.completed` webhook — registered in the dashboard's Integrations Hub — instead of polling.\n\n**Response envelope migration:** during the current migration window, 2xx responses carry the bare payload shown in the example (the value of the schema's `data` property, without the `{\"data\": ...}` wrapper). The `Deprecation` and `Sunset` headers on every 2xx announce this envelope migration — they do **not** deprecate the endpoint itself. After the `Sunset` date, responses will be wrapped as `{\"data\": ...}` exactly as the schema declares.","tags":["Classic vocabulary (aliases)"],"operationId":"inputapi_get_survey_analysis","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SurveyAnalysisReadResponse"}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```
