# Bulk update match predictions

Change up to 100 match predictions in one request, as a background job that applies each change like Patch match prediction.

- Endpoint: `POST https://api.returning.ai/v1/match-predictions/bulk-updates`
- Section: Gamification / Match predictions
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `matchPredictions` (Shown in the dashboard as "Match Prediction")
- Retries: Idempotency-Key required; same key and body returns the same job
- Guide: hand-written
- Verified: code, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/gamification-match-predictions/bulk-update-match-predictions

## When to use this

- A round of fixtures is rescheduled, and many kickoff times move.
- Your sports data feed sends a batch of corrections to titles or times.
- You set up many drafts and want to change their rewards before publishing.

**Instead:** Use [Patch match prediction](https://docs.returning.ai/api-reference/gamification-match-predictions/patch-match-prediction.md) instead for one match, to get the result straight away.

**Availability:** api.returning.ai doesn't serve this route yet: requests return an empty `404`. Ask Returning.AI before you build on it.

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `matchPredictions`

Use a Community API key with `matchPredictions`, shown in the dashboard as Match Prediction, and keep it on your server. The key decides the community: you only see and change match predictions in that community, and an ID from another community returns `404`, the same as an unknown ID. Personal API keys are rejected with `403`.

## Behaviour

api.returning.ai doesn't serve this route yet: requests return an empty `404`. Ask Returning.AI before you build on it.

A `202` means the job is queued; the changes haven't been made yet. A job can wait a while before it starts. Each item is then applied in order with the same rules as Patch match prediction: the `version` must match, fields locked after publishing stay locked, and published times only move later. An item that fails doesn't undo the others, and the job goes on to the next item.

One difference: the job doesn't check the order of the times, that `kickoffAt` is in the future, or that `entryCost.coins` is `0` when entry cost is off. Patch match prediction rejects those; here they are saved. Check them before you send.

A bulk update only changes fields. It can't publish, settle or void a match. Job records are kept for 7 days.

## Request

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `matchPredictions`. (`Bearer <API_KEY>`) |
| `Idempotency-Key` | `string` | Yes | A unique key you create for this job, such as a UUID. Reuse it only to retry the same body. (1-255 characters) |
| `Content-Type` | `string` | Yes | Request body format. Anything else returns `415`. (`application/json`) |
| `X-Request-Id` | `string` | No | Your own ID for this request, returned in `meta.requestId`. Without it, a `req_...` ID is generated. |

### Body

Send `items`, 1-100 of them, each with `matchPredictionId`, `version` and `changes`. A match can appear only once. `changes` takes the same fields as Patch match prediction, without `version`; an unknown field in `changes` rejects the whole request, while an unknown field on an item itself is ignored.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | `object[]` | Yes | The changes, one item per match prediction. Applied in this order. (1-100 items; each match once) |
| `items.matchPredictionId` | `string` | Yes | The match prediction to change. An unknown ID fails that item only. (`mp_` and 24 hex characters) |
| `items.version` | `integer` | Yes | The `version` you last read for this match. (Whole number, 1 or more) |
| `items.changes` | `object` | Yes | The fields to change, as in Patch match prediction but without `version`. At least one field. |

### Example request

```bash
curl --request POST \
  --url https://api.returning.ai/v1/match-predictions/bulk-updates \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Idempotency-Key: 3f9c2a7e-1b4d-4e8a-9c61-2d5f7a8b9e10' \
  --header 'Content-Type: application/json' \
  --data '{
    "items": [
      {
        "matchPredictionId": "mp_66f000000000000000000801",
        "version": 2,
        "changes": {
          "lockAt": "2026-10-10T19:25:00.000Z",
          "kickoffAt": "2026-10-10T19:30:00.000Z"
        }
      },
      {
        "matchPredictionId": "mp_66f000000000000000000807",
        "version": 1,
        "changes": {
          "title": "Japan vs Fiji"
        }
      }
    ]
  }'
```

## Response

A `202` returns the job ID as `operationId`, with the URLs of its status and results.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `meta` | `object` | always | Result details. |
| `meta.status` | `string` | always | Result of the request. (`success`) |
| `meta.statusCode` | `integer` | always | The HTTP status, repeated. |
| `meta.code` | `string` | always | Machine-readable result code. (`MATCH_PREDICTION_BULK_UPDATE_ACCEPTED`) |
| `meta.requestId` | `string` | always | Your `X-Request-Id` if you sent one, otherwise a generated `req_...` ID. Quote it when you contact support. |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | The job. |
| `data.operationId` | `string` | always | The job ID. Use it with the job status and results endpoints. (`mpbu_` and 32 hex characters) |
| `data.type` | `string` | always | Always `bulk_update`. |
| `data.status` | `string` | always | Always `queued` in this response, even on a replay. Check the job status for the real state. |
| `data.submitted` | `integer` | always | Items in the job. |
| `data.statusUrl` | `string` | always | Where to check the job, without the host. |
| `data.resultsUrl` | `string` | always | Where to read each item's result, without the host. |

### Example response (202)

```json
{
  "meta": {
    "status": "success",
    "statusCode": 202,
    "code": "MATCH_PREDICTION_BULK_UPDATE_ACCEPTED",
    "requestId": "req_66f00000000000000000000000000811"
  },
  "message": "Bulk update accepted.",
  "data": {
    "operationId": "mpbu_66f00000000000000000000000000802",
    "type": "bulk_update",
    "status": "queued",
    "submitted": 2,
    "statusUrl": "/v1/match-predictions/bulk-jobs/mpbu_66f00000000000000000000000000802",
    "resultsUrl": "/v1/match-predictions/bulk-jobs/mpbu_66f00000000000000000000000000802/results"
  }
}
```

## Errors

Every error carries its code in `meta.code` and the request's ID in `meta.requestId`. Validation errors list each problem in `details`, with a `field`, `code` and `message`. The API key checks (`401`, `403`, `404 COMMUNITY_NOT_FOUND` and `500 AUTHENTICATION_FAILED`) return `detail` and `solution` instead. A `404` with an empty body means the route isn't available on api.returning.ai yet. Problems with single items don't appear here; read them with Get bulk job results.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 404 | - | The body is empty: api.returning.ai doesn't serve this route yet. Ask Returning.AI before you build on it. |
| 400 | `MATCH_PREDICTION_VALIDATION_FAILED` | The request as a whole is invalid. `details[].code` says why: `EMPTY_ITEMS` (no items), `TOO_MANY_ITEMS` (over 100), `DUPLICATE_ITEM_ID` (a match appears twice), `EMPTY_UPDATE` (an item changes nothing), or a field that breaks its rule or isn't recognised. No job was created. |
| 400 | `READ_ONLY_FIELD` | The request has a field the server sets, such as `communityId` or `serverId`, at the top of the body. `details` names it. Remove it. |
| 400 | `INVALID_JSON` | The body is not valid JSON. Fix the syntax and send it again. |
| 400 | `IDEMPOTENCY_KEY_REQUIRED` | Send an `Idempotency-Key` header with a unique value. |
| 400 | `IDEMPOTENCY_KEY_INVALID` | The `Idempotency-Key` is longer than 255 characters. Use a shorter key, such as a UUID. |
| 415 | `UNSUPPORTED_MEDIA_TYPE` | Send the body as JSON with `Content-Type: application/json`. |
| 401 | `AUTHENTICATION_REQUIRED` | The key is missing, invalid or expired. Send `Authorization: Bearer <API_KEY>` with a current Community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key lacks `matchPredictions`, or it is a personal key. Use a Community API key and add Match Prediction in Settings > Integration > API Keys. |
| 409 | `IDEMPOTENCY_KEY_CONFLICT` | This `Idempotency-Key` was already used with a different body. Use a new key for a new body. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `COMMUNITY_NOT_FOUND` | The community this key belongs to no longer exists. Contact Returning.AI support. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 429 | `RATE_LIMITED` | Your community is over its [rate limit](https://docs.returning.ai/how-it-works.md#rate-limits). Wait for the seconds in `Retry-After`, then retry. |
| 500 | `INTERNAL_ERROR` | The outcome is unclear. Send the same body again with the same `Idempotency-Key`: if the job was created, you get it back. |
| 500 | `AUTHENTICATION_FAILED` | The key could not be checked. Retry with backoff; nothing changed. |
| 503 | `MATCH_PREDICTION_BULK_UPDATE_QUEUE_UNAVAILABLE` | The job couldn't be queued and was removed. Retry later with a new `Idempotency-Key`: the same key returns this `503` again. |
| 503 | `MATCH_PREDICTION_FROZEN` | Returning.AI has paused match prediction requests. Nothing changed. Retry later with backoff. |

**Retries:** Every bulk update needs an `Idempotency-Key`. The same key with the same body returns the same job, for as long as the job is kept, and never runs it twice. The same key with a different body returns `409 IDEMPOTENCY_KEY_CONFLICT`, except that items differing only in `predictionStartAt`, `lockAt` or `kickoffAt` count as the same body. Use a new key whenever you change anything. After a `503`, use a new key. Returning.AI can set a request limit for your community on these endpoints, counted per community and calling IP address. Over it, requests return `429 RATE_LIMITED` with a `Retry-After` header in seconds; wait that long, then retry.

## Next step

- [Get bulk job status](https://docs.returning.ai/api-reference/gamification-match-predictions/get-bulk-job-status.md): `GET /v1/match-predictions/bulk-jobs/{jobId}`. Check the job with its `operationId` until it is `completed` or `completed_with_errors`.
- [Read each item's result with Get bulk job results](https://docs.returning.ai/api-reference/gamification-match-predictions/get-bulk-job-results.md): `GET /v1/match-predictions/bulk-jobs/{jobId}/results`.
