> ## Documentation Index
> Fetch the complete documentation index at: https://docs.retailgrid.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish competitor data to Price Radar

> Publish raw or already-matched competitor CSVs with an API key and follow their progress and row refusals.

Use the Matching API to refresh your organization's Price Radar from a scheduled scraper, matching pipeline, or partner integration. It accepts the same raw and already-matched competitor exports as **Update** on the [Competitor matching](https://docs.retailgrid.io/price-monitoring/competitor-matching.md) page, with the same validation, coverage controls, and publication behavior.

Both upload and progress requests use your [Public API bearer key](https://docs.retailgrid.io/api-reference/authentication.md). A signed-in browser session is not required.

## Choose the right upload

| Your goal | Endpoint |
| :- | :- |
| Publish competitor data to Price Radar and refresh Competitor matching | `POST /v1/matching/matched-snapshots` |
| Import observations into the Competitor Prices dataset | [`POST /v1/imports/competitor_prices`](https://docs.retailgrid.io/api-reference/imports/competitor-prices) |

The Matching endpoint accepts **one UTF-8 CSV**. The header decides how it is processed:

* **`item_id` is present:** the file is treated as already matched and published against your catalog.
* **`item_id` is absent:** Price Radar matches the raw competitor export to your catalog before publication.
* A raw file must also omit the link columns `external_match_id`, `match_relation`, `match_method`, and `match_confidence`. A file containing any of those columns without `item_id` is refused.

The `matched-snapshots` name covers both file kinds; there is no separate raw-upload endpoint or upload-type parameter. Use the same export format you would upload on the Competitor matching page. The CSV header must fit within 64 KiB.

## Upload a file

```bash theme={null}
curl -X POST "https://clientapi.retailgrid.io/v1/matching/matched-snapshots" \
  -H "Authorization: Bearer $RETAILGRID_API_KEY" \
  -F "file=@competitor-export.csv" \
  -F "coverage=partial" \
  -F "covered_competitors=Shop A,Shop B" \
  -F "allow_large_removal=false"
```

The **entire multipart request must be at most 25 MiB** (26,214,400 bytes), including form fields and multipart overhead. Keep the file below that total. Let `curl` set the multipart `Content-Type` and boundary.

| Form field | Required | Default | Meaning |
| :- | :- | :- | :- |
| `file` | Yes | — | One raw or already-matched competitor CSV. |
| `coverage` | No | `partial` | `partial` or `full`; controls which existing matches the delivery may retire. |
| `covered_competitors` | No | Empty | Comma-separated competitor names, up to 500 names. Used for partial coverage. |
| `allow_large_removal` | No | `false` | Boolean confirmation to permit a delivery that triggers the large-removal guard. |

For **partial** coverage, prices belonging to competitors outside `covered_competitors` remain in place. A partial delivery with no covered competitors adds what it carries and retires nothing. For **full** coverage, the file declares the whole set and may retire matches it omits; `covered_competitors` does not narrow that declaration.

> [!WARNING]
>
> Choose `full` only when the file is a complete delivery. Check the export before setting `allow_large_removal=true`; this confirmation lets an unusually large removal proceed.

A successful upload returns **`202 Accepted`** and a job handle:

```json theme={null}
{
  "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b"
}
```

This means the publish was queued. Validation, matching for raw files, and publication continue in the background. Keep the job ID and follow it to a terminal status.

## Follow publication

Poll the dedicated Matching progress endpoint with the same API key:

```bash theme={null}
curl "https://clientapi.retailgrid.io/v1/matching/jobs/0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b/progress" \
  -H "Authorization: Bearer $RETAILGRID_API_KEY"
```

This endpoint includes progress, counts, and row refusals. Use it for these jobs rather than the generic `/v1/jobs/{job_id}` endpoint.

```json theme={null}
{
  "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b",
  "status": "completed",
  "progress": 100,
  "current_step": "Completed",
  "error_message": null,
  "created_at": "2026-10-22T10:00:00Z",
  "started_at": "2026-10-22T10:00:01Z",
  "completed_at": "2026-10-22T10:01:30Z",
  "result_summary": {
    "accepted": 99,
    "rejected": 1,
    "sent": 100,
    "row_errors": [
      {
        "line": 3,
        "code": "no_catalogue_match",
        "message": "No match",
        "item_id": ""
      }
    ]
  }
}
```

`status` is `queued`, `running`, `cancelling`, `completed`, `failed`, or `cancelled`. The last three are terminal. Poll every **5–10 seconds** while running and every **30–60 seconds** while queued; polling draws on the organization's [shared usage budget](https://docs.retailgrid.io/api-reference/usage-limits.md).

| Response field | Meaning |
| :- | :- |
| `progress` | Percentage from 0 to 100. |
| `current_step` | A public status label, such as `Processing import` or `Completed`. Use `status` and numeric `progress` to track the job. |
| `error_message` | Known validation feedback when available, or a generic message for unexpected failures; may be `null`. |
| `created_at`, `started_at`, `completed_at` | UTC timestamps. Start and completion times may be `null`. |
| `result_summary` | Result counts and refusal details when available; may be `null` before a result exists. |

Within `result_summary`, `accepted` is the accepted row count, `rejected` is the total refused row count, and `sent` is the total processed row count. Inspect these counts even when `status` is `completed`: a published file can contain refused rows.

`row_errors` contains a **sample of at most 200 refusals**, each with `line`, `code`, `message`, and `item_id`. The item ID can be empty, for example when a raw row has no catalog match. This is not a full reject report. The sample can be shorter than `rejected`, or empty if refusal details could not be retrieved; use `rejected` for the total.

## Handle conflicts and failures

| Status | Meaning | Action |
| :- | :- | :- |
| `401` | Public API key missing or invalid. | Check [Authentication](https://docs.retailgrid.io/api-reference/authentication.md). |
| `404` on progress | Matching job not found in your organization, including an unknown ID or another job type. | Check the job ID returned by this upload. |
| `409` on upload | Your organization already has a matching publish in flight. | Wait for it to finish before uploading another file. |
| `413` | The upload exceeds the request size limit. | Reduce the request below 25 MiB. |
| `422` | The file field or multipart options are invalid. | Correct the request before resubmitting. |
| `429` | Rate or organization usage limit reached. | Honor `Retry-After`; see [Usage & limits](https://docs.retailgrid.io/api-reference/usage-limits.md). |

CSV validation and upstream publication failures can happen after `202`; they appear as a terminal `failed` job. Check `error_message` and available row refusals, correct the input or wait for a temporary service problem to clear, then submit a new upload.

Publication and progress are shared by the organization. Another API-key caller in the same organization can follow the job; a caller in another organization cannot. Only one matching delivery runs at a time across API and in-app uploads.

## Related

* [Publish a matching snapshot](https://docs.retailgrid.io/api-reference/matching/publish-snapshot.md)
* [Get matching publish progress](https://docs.retailgrid.io/api-reference/matching/get-progress.md)
* [Competitor matching](https://docs.retailgrid.io/price-monitoring/competitor-matching.md)
* [Imports vs bulk](https://docs.retailgrid.io/api-reference/imports-vs-bulk.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.