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

# Pushing feedback

Everything about getting data in, beyond the five-minute version in [Getting started](/boundaryai-docs/api-and-webhooks/getting-started.md): which ingestion path to use, how deduplication and back-dating behave, and what happens after the push.

***

### Three ways in

| Path                       | Use when                                                     | Behaviour                                                                |
| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `POST /feedback/push`      | A steady trickle: each ticket, review, or call as it closes. | Synchronous; the response reports `inserted` and `skipped_duplicates`.   |
| `POST /feedback/push/bulk` | Backfills and batch jobs, many fields in one call.           | Async **202** with a `task_id` and `status_url`; poll until `completed`. |
| `POST /feedback/upload`    | You already have a CSV or XLSX export.                       | Multipart upload; same async task flow as bulk.                          |

All three land in the same place and trigger the same analysis. Prefer bulk over looping single pushes: one call for thousands of items is friendlier to your rate limits (60/minute per key by default) and to the platform.

***

### Structured items: the fields that earn their keep

An item in `content` can be a bare string, but production integrations should send objects:

```json
{"text": "Support resolved my issue in one call.",
 "external_id": "ticket-58121",
 "customer_id": "cus_310",
 "channel": "support",
 "language": "en",
 "rating": 5,
 "occurred_at": "2026-07-12T09:30:00Z"}
```

* **`external_id`** is your stable identifier, and it's what makes retries safe: within a field, a second push with the same `external_id` updates rather than duplicates, and the response counts it under `skipped_duplicates` with the IDs in `duplicate_external_ids`. Without it, a replayed batch means duplicate comments polluting your analysis.
* **`occurred_at`** back-dates the item to when the feedback actually happened. This matters most for [time-tracked groups](/boundaryai-docs/analysing-your-feedback/tracking-feedback-over-time-evolution.md): a June ticket pushed in July lands in June's period, not July's. Omitted, items date to arrival time.
* **`customer_id`** ties items to a person across sources, powers [bulk erasure](#erasing-and-listing-what-you-pushed), and pairs with the `external_id` on [invites](/boundaryai-docs/api-and-webhooks/sending-invites.md) so you can join the full loop in your warehouse.
* **`channel`**, **`language`**, **`rating`** become segmentation metadata, exactly like metadata columns on an [upload](/boundaryai-docs/bringing-in-your-feedback/uploading-an-existing-dataset.md).

Two layers of retry safety compose: `external_id` deduplicates at the item level forever, and the **`Idempotency-Key`** header makes an entire request replayable for 24 hours (same key = same response, no re-processing). Use both; networks fail mid-request more often than anyone likes.

***

### What happens after a push

1. Items are cleaned, language-detected, and queued for analysis automatically; there is no "run analysis" call to make.
2. Group monitors with **Auto-cover new sources** on are already watching API-created sources ([Custom Monitoring](/boundaryai-docs/analysing-your-feedback/custom-monitoring.md)), so monitor matches and alerts fire from the first pass.
3. When the pass finishes, the **`analysis.completed`** webhook fires, and `GET /sources/{id}/analysis` returns the sentiment distribution, themes, and monitor matches.
4. The data participates in everything else: Grouped Themes, Evolution periods, reports, and the dashboards, indistinguishable from survey or upload data.

Pushing consumes your organisation's **Usage Allowance** when analysed (the push response's `aps_deducted` shows the charge), the same metering as every other analysis on the platform; see [Settings](/boundaryai-docs/account-and-administration/updating-settings.md#usage).

***

### Erasing and listing what you pushed

You stay in control of the data your integration created:

* `GET /feedback` lists API-pushed items (filter by `source_id` / `field_id`, cursor-paginated), useful for reconciliation jobs.
* `POST /feedback/erase` bulk-deletes by `external_ids` or by `customer_id`, which is the mechanical piece of honouring a GDPR erasure request: one call removes a person's API-pushed feedback across sources. It only touches API-pushed rows; survey responses and uploads have their own product-side deletion flows.

***

### Integration checklist

* One key per integration, `push` scope, `Idempotency-Key` on every write.
* Always send `external_id` and `occurred_at`; your future self doing a re-sync will thank you.
* Use bulk for anything over a handful of items; poll the `status_url` or subscribe to `content.pushed`.
* Subscribe to `analysis.completed` instead of polling the analysis endpoint.
* Map `customer_id` to the same identifier your CRM uses, so erasure and invite joins stay one-call operations.
