> 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/push-feedback.md).

# Push Feedback

Append feedback items to a source's field: single calls for steady trickles, bulk + async status for backfills, or a CSV/XLSX upload. Structured items support `external_id` dedup (retries never double-write), `occurred_at` back-dating, ratings, and per-item metadata.

## Push feedback items to a field

> Partner-vocabulary variant of \`/content/push\`: append feedback items to one field on a source. Accepts the same payload with \`feedback\_group\_id\` / \`source\_id\` / \`field.field\_id\` naming and responds in the same vocabulary. Idempotent via the \`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":"Push feedback","description":"Append feedback items to a source's field: single calls for steady trickles, bulk + async status for backfills, or a CSV/XLSX upload. Structured items support `external_id` dedup (retries never double-write), `occurred_at` back-dating, ratings, and per-item metadata."}],"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":{"FeedbackPushRequest":{"properties":{"feedback_group_id":{"description":"Target feedback group id","title":"Feedback Group Id","type":"string"},"source_id":{"description":"Target source id within the group","title":"Source Id","type":"string"},"field":{"$ref":"#/components/schemas/FieldContentPush"},"source_reference":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"description":"Free-form identifier attached to the push for downstream tracking.","title":"Source Reference"}},"required":["feedback_group_id","source_id","field"],"title":"FeedbackPushRequest","type":"object"},"FieldContentPush":{"properties":{"field_id":{"description":"Target field id (integer as string)","title":"Field Id","type":"string"},"content":{"description":"Feedback items — bare strings or structured items with ingest metadata","items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/ContentItem"}]},"minItems":1,"title":"Content","type":"array"}},"required":["field_id","content"],"title":"FieldContentPush","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"},"FeedbackPushResponse":{"properties":{"status":{"const":"success","title":"Status","type":"string"},"feedback_group_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Feedback group the resolved source belongs to","title":"Feedback Group Id"},"source_id":{"description":"Resolved source id where feedback landed","title":"Source Id","type":"integer"},"field_id":{"description":"Resolved field id where feedback landed","title":"Field Id","type":"integer"},"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,"title":"Skipped Duplicates"},"duplicate_external_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"default":null,"title":"Duplicate External Ids"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Message"}},"required":["status","source_id","field_id","inserted","aps_deducted"],"title":"FeedbackPushResponse","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/feedback/push":{"post":{"summary":"Push feedback items to a field","description":"Partner-vocabulary variant of `/content/push`: append feedback items to one field on a source. Accepts the same payload with `feedback_group_id` / `source_id` / `field.field_id` naming and responds in the same vocabulary. Idempotent via the `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":["Push feedback"],"operationId":"inputapi_post_feedback_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/FeedbackPushRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/FeedbackPushResponse"}},"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 feedback push (async)

> Partner-vocabulary variant of \`/content/push/bulk\`: queue a multi-field feedback batch for async processing. Returns 202 with a \`task\_id\` and partner-path \`status\_url\`. 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":"Push feedback","description":"Append feedback items to a source's field: single calls for steady trickles, bulk + async status for backfills, or a CSV/XLSX upload. Structured items support `external_id` dedup (retries never double-write), `occurred_at` back-dating, ratings, and per-item metadata."}],"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":{"BulkFeedbackPushRequest":{"properties":{"feedback_group_id":{"title":"Feedback Group Id","type":"string"},"source_id":{"title":"Source Id","type":"string"},"fields":{"items":{"$ref":"#/components/schemas/BulkFieldContentPush"},"maxItems":100,"minItems":1,"title":"Fields","type":"array"},"source_reference":{"anyOf":[{"maxLength":255,"type":"string"},{"type":"null"}],"default":null,"title":"Source Reference"}},"required":["feedback_group_id","source_id","fields"],"title":"BulkFeedbackPushRequest","type":"object"},"BulkFieldContentPush":{"properties":{"field_id":{"title":"Field Id","type":"string"},"content":{"items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/ContentItem"}]},"minItems":1,"title":"Content","type":"array"}},"required":["field_id","content"],"title":"BulkFieldContentPush","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"},"BulkFeedbackPushAccepted":{"properties":{"status":{"const":"accepted","title":"Status","type":"string"},"task_id":{"title":"Task Id","type":"string"},"feedback_group_id":{"title":"Feedback Group Id","type":"integer"},"source_id":{"title":"Source Id","type":"integer"},"status_url":{"title":"Status Url","type":"string"}},"required":["status","task_id","feedback_group_id","source_id","status_url"],"title":"BulkFeedbackPushAccepted","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/feedback/push/bulk":{"post":{"summary":"Submit a bulk feedback push (async)","description":"Partner-vocabulary variant of `/content/push/bulk`: queue a multi-field feedback batch for async processing. Returns 202 with a `task_id` and partner-path `status_url`. 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":["Push feedback"],"operationId":"inputapi_post_feedback_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/BulkFeedbackPushRequest"}}}},"responses":{"202":{"description":"Accepted — task queued for async processing","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BulkFeedbackPushAccepted"}},"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 feedback push

> Partner-vocabulary variant of \`/content/push/bulk/status/{task\_id}\`. 202 while pending/processing, 200 on completed or failed (the \`status\` field is the discriminator). 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":"Push feedback","description":"Append feedback items to a source's field: single calls for steady trickles, bulk + async status for backfills, or a CSV/XLSX upload. Structured items support `external_id` dedup (retries never double-write), `occurred_at` back-dating, ratings, and per-item metadata."}],"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":{"BulkFeedbackPushStatus":{"properties":{"task_id":{"title":"Task Id","type":"string"},"status":{"enum":["pending","processing","completed","failed"],"title":"Status","type":"string"},"feedback_group_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Feedback Group Id"},"source_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Source 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,"title":"Created At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Completed At"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"description":"Present on status=completed: {status, feedback_group_id, source_id, total_inserted, total_rows_accepted, total_aps_deducted, fields: [{field_id, inserted, status}, ...]}","title":"Result"},"error":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Error"}},"required":["task_id","status"],"title":"BulkFeedbackPushStatus","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/feedback/push/bulk/status/{task_id}":{"get":{"summary":"Poll the status of a bulk feedback push","description":"Partner-vocabulary variant of `/content/push/bulk/status/{task_id}`. 202 while pending/processing, 200 on completed or failed (the `status` field is the discriminator). 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":["Push feedback"],"operationId":"inputapi_get_feedback_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/BulkFeedbackPushStatus"}},"required":["data"]}}}},"202":{"description":"Accepted — task queued for async processing","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BulkFeedbackPushStatus"}},"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"}}}}}}
```

## Upload a CSV/XLSX of feedback (async ingest)

> Multipart upload: \`file\` (.csv or .xlsx) plus \`feedback\_group\_id\`, \`source\_id\` and \`field\_id\` form fields. The text column is picked with \`column\` (header name; defaults to the first column). Optional \`\<key>\_column\` fields (external\_id\_column, customer\_id\_column, channel\_column, language\_column, rating\_column, occurred\_at\_column) map spreadsheet columns onto the structured ingest metadata, including external-ID dedup. Returns 202 with a \`task\_id\`; poll the bulk status endpoint. Idempotent via the \`Idempotency-Key\` header (24h window) — a retried upload of the same file replays the original 202 instead of re-ingesting the rows.\
> \
> \*\*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":"Push feedback","description":"Append feedback items to a source's field: single calls for steady trickles, bulk + async status for backfills, or a CSV/XLSX upload. Structured items support `external_id` dedup (retries never double-write), `occurred_at` back-dating, ratings, and per-item metadata."}],"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":{"BulkFeedbackPushAccepted":{"properties":{"status":{"const":"accepted","title":"Status","type":"string"},"task_id":{"title":"Task Id","type":"string"},"feedback_group_id":{"title":"Feedback Group Id","type":"integer"},"source_id":{"title":"Source Id","type":"integer"},"status_url":{"title":"Status Url","type":"string"}},"required":["status","task_id","feedback_group_id","source_id","status_url"],"title":"BulkFeedbackPushAccepted","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/feedback/upload":{"post":{"summary":"Upload a CSV/XLSX of feedback (async ingest)","description":"Multipart upload: `file` (.csv or .xlsx) plus `feedback_group_id`, `source_id` and `field_id` form fields. The text column is picked with `column` (header name; defaults to the first column). Optional `<key>_column` fields (external_id_column, customer_id_column, channel_column, language_column, rating_column, occurred_at_column) map spreadsheet columns onto the structured ingest metadata, including external-ID dedup. Returns 202 with a `task_id`; poll the bulk status endpoint. Idempotent via the `Idempotency-Key` header (24h window) — a retried upload of the same file replays the original 202 instead of re-ingesting the rows.\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":["Push feedback"],"operationId":"inputapi_post_feedback_upload","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Opaque client-supplied key for safe retries (24h window)."}],"responses":{"202":{"description":"Accepted — task queued for async processing","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BulkFeedbackPushAccepted"}},"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"}}}},"413":{"description":"Payload Too Large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```
