> 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/feedback-groups-and-sources.md).

# Feedback Groups & Sources

Create the containers your data lands in: a **feedback group** holds everything about one programme, and each **source** inside it holds one stream of feedback with typed **fields**. Sources are created in draft mode; publish one to start pushing content.

## Create a feedback group

> Partner-vocabulary variant of \`/survey\_series/create\`. Idempotent on group name within the org — if a group with the same name already exists, returns 200 with \`created: false\` and the existing group 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":"Feedback groups & sources","description":"Create the containers your data lands in: a **feedback group** holds everything about one programme, and each **source** inside it holds one stream of feedback with typed **fields**. Sources are created in draft mode; publish one to start pushing content."}],"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":{"CreateFeedbackGroupRequest":{"properties":{"name":{"description":"Feedback group display name","maxLength":255,"minLength":2,"title":"Name","type":"string"}},"required":["name"],"title":"CreateFeedbackGroupRequest","type":"object"},"CreateFeedbackGroupResponse":{"properties":{"status":{"const":"success","title":"Status","type":"string"},"created":{"description":"False when the group already existed","title":"Created","type":"boolean"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Message"},"feedback_group":{"$ref":"#/components/schemas/CreatedFeedbackGroupMinimal"}},"required":["status","created","feedback_group"],"title":"CreateFeedbackGroupResponse","type":"object"},"CreatedFeedbackGroupMinimal":{"properties":{"id":{"title":"Id","type":"integer"},"name":{"title":"Name","type":"string"}},"required":["id","name"],"title":"CreatedFeedbackGroupMinimal","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_groups/create":{"post":{"summary":"Create a feedback group","description":"Partner-vocabulary variant of `/survey_series/create`. Idempotent on group name within the org — if a group with the same name already exists, returns 200 with `created: false` and the existing group 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":["Feedback groups & sources"],"operationId":"inputapi_post_feedback_groups_create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFeedbackGroupRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateFeedbackGroupResponse"}},"required":["data"]}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateFeedbackGroupResponse"}},"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"}}}}}}
```

## Create a source inside a feedback group

> Partner-vocabulary variant of \`/survey/create\`. Creates a new source in EDITING mode with the supplied fields; not publicly accessible until \`/sources/publish\` is called. Field 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":"Feedback groups & sources","description":"Create the containers your data lands in: a **feedback group** holds everything about one programme, and each **source** inside it holds one stream of feedback with typed **fields**. Sources are created in draft mode; publish one to start pushing content."}],"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":{"CreateSourceRequest":{"properties":{"feedback_group_id":{"title":"Feedback Group Id","type":"string"},"source_title":{"maxLength":100,"minLength":1,"title":"Source Title","type":"string"},"source_description":{"default":"","maxLength":1000,"title":"Source 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.","title":"Feedback Type","type":"string"},"fields":{"items":{"$ref":"#/components/schemas/CreateSourceField"},"maxItems":100,"title":"Fields","type":"array"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"EN | FR | ES (case-insensitive)","title":"Language"}},"required":["feedback_group_id","source_title"],"title":"CreateSourceRequest","type":"object"},"CreateSourceField":{"properties":{"field_title":{"maxLength":500,"minLength":1,"title":"Field Title","type":"string"},"field_type":{"description":"One of DEPTH_TEXT, TEXT, MCQ, SCQ, RATING, NPS, METADATA (case-insensitive; also accepts long_answer / short_answer).","title":"Field Type","type":"string"},"field_options":{"items":{"type":"string"},"maxItems":100,"title":"Field 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":["field_title","field_type"],"title":"CreateSourceField","type":"object"},"CreateSourceResponse":{"properties":{"status":{"const":"success","title":"Status","type":"string"},"feedback_group_id":{"title":"Feedback Group Id","type":"integer"},"source_id":{"title":"Source Id","type":"integer"},"feedback_type":{"default":"survey","title":"Feedback Type","type":"string"},"fields_created":{"title":"Fields Created","type":"integer"},"fields":{"items":{"$ref":"#/components/schemas/CreatedField"},"title":"Fields","type":"array"},"message":{"title":"Message","type":"string"}},"required":["status","feedback_group_id","source_id","fields_created","fields","message"],"title":"CreateSourceResponse","type":"object"},"CreatedField":{"properties":{"id":{"title":"Id","type":"integer"},"title":{"title":"Title","type":"string"},"type":{"title":"Type","type":"string"}},"required":["id","title","type"],"title":"CreatedField","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/sources/create":{"post":{"summary":"Create a source inside a feedback group","description":"Partner-vocabulary variant of `/survey/create`. Creates a new source in EDITING mode with the supplied fields; not publicly accessible until `/sources/publish` is called. Field 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":["Feedback groups & sources"],"operationId":"inputapi_post_sources_create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSourceRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateSourceResponse"}},"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"}}}}}}
```

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

> Partner-vocabulary variant of \`/surveys/list\`: feedback groups with their sources and fields, including \`accepts\_content\`, \`content\_format\` and \`mode\`. Filterable by \`feedback\_group\_id\` and \`source\_id\`; paginated at the feedback-group level via \`limit\`/\`offset\`.\
> \
> \*\*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":"Feedback groups & sources","description":"Create the containers your data lands in: a **feedback group** holds everything about one programme, and each **source** inside it holds one stream of feedback with typed **fields**. Sources are created in draft mode; publish one to start pushing content."}],"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":{"SourcesListResponse":{"description":"Body of GET /api/input/sources/list (partner-vocabulary variant).","properties":{"feedback_groups":{"items":{"$ref":"#/components/schemas/FeedbackGroupListing"},"title":"Feedback Groups","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":"SourcesListResponse","type":"object"},"FeedbackGroupListing":{"properties":{"id":{"title":"Id","type":"integer"},"name":{"title":"Name","type":"string"},"sources":{"items":{"$ref":"#/components/schemas/SourceListing"},"title":"Sources","type":"array"}},"required":["id","name"],"title":"FeedbackGroupListing","type":"object"},"SourceListing":{"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"},"fields":{"items":{"$ref":"#/components/schemas/FieldListing"},"title":"Fields","type":"array"}},"required":["id","title"],"title":"SourceListing","type":"object"},"FieldListing":{"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":"FieldListing","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/sources/list":{"get":{"summary":"List sources accessible to the API key's organization","description":"Partner-vocabulary variant of `/surveys/list`: feedback groups with their sources and fields, including `accepts_content`, `content_format` and `mode`. Filterable by `feedback_group_id` and `source_id`; paginated at the feedback-group level via `limit`/`offset`.\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":["Feedback groups & sources"],"operationId":"inputapi_get_sources_list","parameters":[{"name":"feedback_group_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"source_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 feedback groups returned per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Number of feedback groups to skip."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SourcesListResponse"}},"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"}}}}}}
```

## Publish a source (move to ACCESSIBLE mode)

> Partner-vocabulary variant of \`/survey/publish\`. Idempotent: publishing an already-ACCESSIBLE source 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":"Feedback groups & sources","description":"Create the containers your data lands in: a **feedback group** holds everything about one programme, and each **source** inside it holds one stream of feedback with typed **fields**. Sources are created in draft mode; publish one to start pushing content."}],"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":{"PublishSourceRequest":{"properties":{"source_id":{"title":"Source Id","type":"integer"},"feedback_group_id":{"title":"Feedback Group Id","type":"integer"}},"required":["source_id","feedback_group_id"],"title":"PublishSourceRequest","type":"object"},"PublishSourceResponse":{"properties":{"status":{"const":"success","title":"Status","type":"string"},"source_id":{"title":"Source Id","type":"integer"},"feedback_group_id":{"title":"Feedback Group Id","type":"integer"},"mode":{"const":"ACCESSIBLE","title":"Mode","type":"string"},"message":{"title":"Message","type":"string"}},"required":["status","source_id","feedback_group_id","mode","message"],"title":"PublishSourceResponse","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/sources/publish":{"post":{"summary":"Publish a source (move to ACCESSIBLE mode)","description":"Partner-vocabulary variant of `/survey/publish`. Idempotent: publishing an already-ACCESSIBLE source 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":["Feedback groups & sources"],"operationId":"inputapi_post_sources_publish","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSourceRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PublishSourceResponse"}},"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"}}}}}}
```
