# Patch match prediction

Change fields on one match prediction, guarded by its version so you never overwrite someone else's change.

- Endpoint: `PATCH https://api.returning.ai/v1/match-predictions/{matchPredictionId}`
- Section: Gamification / Match predictions
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `matchPredictions` (Shown in the dashboard as "Match Prediction")
- Retries: No Idempotency-Key; read back before retrying
- Guide: hand-written
- Verified: code, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/gamification-match-predictions/patch-match-prediction

## When to use this

- Fix a draft's teams, rewards or audience before you publish it.
- A fixture is delayed, and kickoff and the prediction deadline should move later.
- Correct a match title or description that traders already see.

**Instead:** Use [Bulk update match predictions](https://docs.returning.ai/api-reference/gamification-match-predictions/bulk-update-match-predictions.md) instead to change many matches in one request.

**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.

Only the fields you send change. `teamA`, `teamB`, `rewards`, `entryCost` and `viewPermission` are replaced as a whole, so send every part of them. Each change adds 1 to `version`.

While the match is a `draft`, every field can change. Once it is published, only `title`, `description`, `externalReference` and the three times can change; anything else returns `409 LOCKED_FIELDS_IMMUTABLE`. While a published match is `scheduled`, `open` or `locked`, its times can only move later.

The time order is checked only among the times in the same request, and `kickoffAt` must be in the future whenever you send it. When you move kickoff, send `lockAt` in the same request so the order stays right.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `matchPredictionId` | `string` | Yes | - |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `matchPredictions`. (`Bearer <API_KEY>`) |
| `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 `version` and at least one other field. Time and other rules are the same as for Create match prediction. `id`, `status` and `communityId` return `409 LOCKED_FIELDS_IMMUTABLE`; other fields the server sets, such as `createdAt`, return `400 READ_ONLY_FIELD`.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `version` | `integer` | Yes | The `version` you last read. The change is rejected if the match has changed since. (Whole number, 1 or more) |
| `title` | `string` | No | The match title traders see. (1-256 characters, trimmed) |
| `description` | `string` | No | Longer text for traders. (Up to 10,000 characters) |
| `externalReference` | `string` | No | Your own fixture ID. Unique in your community, so it is the safest way to find the match again. (1-128 characters, trimmed) |
| `teamA` | `object` | No | The first team. Replaced as a whole. |
| `teamA.name` | `string` | Yes | Team name. (1-128 characters) |
| `teamA.imageUrl` | `string` | No | Team logo URL. (A URL, up to 2,048 characters) |
| `teamB` | `object` | No | The second team. Replaced as a whole. |
| `teamB.name` | `string` | Yes | Team name. (1-128 characters) |
| `teamB.imageUrl` | `string` | No | Team logo URL. (A URL, up to 2,048 characters) |
| `predictionStartAt` | `string` | No | When traders can start predicting. (ISO 8601 in UTC ending in `Z`; at or before `lockAt`) |
| `lockAt` | `string` | No | When predictions close. (ISO 8601 in UTC ending in `Z`; at or before `kickoffAt`) |
| `kickoffAt` | `string` | No | When the match starts. You can settle only after this time. (ISO 8601 in UTC ending in `Z`; in the future) |
| `scoreBasis` | `string` | No | Which score you will settle on. (`regulation` or `extra_time`) |
| `rewards` | `object` | No | What winners earn. Send both tiers. |
| `rewards.correctResult` | `object` | Yes | Paid for picking the right result: team A, team B or a draw. |
| `rewards.correctResult.coins` | `integer` | Yes | Coins. (Whole number, 0-1,000,000) |
| `rewards.correctResult.xp` | `integer` | Yes | XP. (Whole number, 0-1,000,000) |
| `rewards.exactScoreBonus` | `object` | Yes | Paid on top of `correctResult` when the called score is also exact. |
| `rewards.exactScoreBonus.coins` | `integer` | Yes | Coins. (Whole number, 0-1,000,000) |
| `rewards.exactScoreBonus.xp` | `integer` | Yes | XP. (Whole number, 0-1,000,000) |
| `entryCost` | `object` | No | What a trader pays to predict. Winners get it back at settlement, and everyone gets it back if you void. |
| `entryCost.enabled` | `boolean` | Yes | `true` to charge coins for each prediction. |
| `entryCost.coins` | `integer` | Yes | Coins per prediction. (Whole number; `0` when `enabled` is `false`) |
| `viewPermission` | `object` | No | Who can see the match once it is published. Can be empty on a draft; publishing needs at least one role or user. |
| `viewPermission.roleIds` | `string[]` | Yes | Role IDs from your community. Traders with any of these roles can see it. (Role IDs in your community) |
| `viewPermission.userIds` | `string[]` | Yes | Platform user IDs, as strings, of traders who can see it. (Members of your community) |

### Example request

```bash
curl --request PATCH \
  --url https://api.returning.ai/v1/match-predictions/mp_66f000000000000000000801 \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "version": 1,
    "lockAt": "2026-10-10T19:25:00.000Z",
    "kickoffAt": "2026-10-10T19:30:00.000Z"
  }'
```

## Response

A `200` returns the whole match prediction after the change, with the new `version`. Keep it for your next change.

### 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_UPDATED`) |
| `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 match prediction after the change. |
| `data.id` | `string` | always | The match prediction ID. Use it in every other call. (`mp_` and 24 hex characters) |
| `data.externalReference` | `string` | - | Your own fixture ID. Left out when not set. |
| `data.title` | `string` | always | The match title traders see. |
| `data.description` | `string` | - | Longer text for traders. Left out when not set. |
| `data.teamA` | `object` | always | The first team. |
| `data.teamA.name` | `string` | always | Team name. |
| `data.teamA.imageUrl` | `string` | - | Team logo URL. Left out when not set. |
| `data.teamB` | `object` | always | The second team. |
| `data.teamB.name` | `string` | always | Team name. |
| `data.teamB.imageUrl` | `string` | - | Team logo URL. Left out when not set. |
| `data.predictionStartAt` | `string` | always | When traders can start predicting, in UTC. Milliseconds always read `.000`. |
| `data.lockAt` | `string` | always | When predictions close, in UTC. |
| `data.kickoffAt` | `string` | always | When the match starts, in UTC. |
| `data.scoreBasis` | `string` | always | Which score you settle on. Settlement uses the score you send either way. (`regulation` or `extra_time`) |
| `data.viewPermission` | `object` | always | Who can see the match once it is published. |
| `data.viewPermission.roleIds` | `string[]` | always | Role IDs. Traders with any of these roles can see it. |
| `data.viewPermission.userIds` | `string[]` | always | Platform user IDs, as strings, of traders who can see it. |
| `data.rewards` | `object` | always | What winners earn. |
| `data.rewards.correctResult` | `object` | always | Paid for picking the right result: team A, team B or a draw. |
| `data.rewards.correctResult.coins` | `integer` | always | Coins. |
| `data.rewards.correctResult.xp` | `integer` | always | XP. |
| `data.rewards.exactScoreBonus` | `object` | always | Paid on top of `correctResult` when the called score is also exact. |
| `data.rewards.exactScoreBonus.coins` | `integer` | always | Coins. |
| `data.rewards.exactScoreBonus.xp` | `integer` | always | XP. |
| `data.entryCost` | `object` | always | What a trader pays to predict. |
| `data.entryCost.enabled` | `boolean` | always | `true` when predicting costs coins. |
| `data.entryCost.coins` | `integer` | always | Coins per prediction. `0` when `enabled` is `false`. |
| `data.status` | `string` | always | `draft` until published. Then `scheduled` before `predictionStartAt`, `open` while traders can predict and `locked` from `lockAt`, all worked out from the clock. The rest follow a settle or void. (`draft`, `scheduled`, `open`, `locked`, `settlement_in_progress`, `settled`, `settlement_failed`, `void_in_progress`, `voided` or `void_failed`) |
| `data.isPublished` | `boolean` | always | `true` once published. |
| `data.acceptingPredictions` | `boolean` | always | `true` while the match is `open` and the time is between `predictionStartAt` and `lockAt`. |
| `data.attemptCount` | `integer` | always | Predictions traders have submitted. |
| `data.settlement` | `object` | always | Always `null` here. Read the match with Get match prediction for its settlement totals. |
| `data.version` | `integer` | always | The current version. Send it with Patch match prediction. It goes up with every change, including publish, settle and void. |
| `data.createdAt` | `string` | always | When the match prediction was created. |
| `data.updatedAt` | `string` | always | When it last changed. |

### Example response (200)

```json
{
  "meta": {
    "status": "success",
    "statusCode": 200,
    "code": "MATCH_PREDICTION_UPDATED",
    "requestId": "req_66f00000000000000000000000000811"
  },
  "message": "Match prediction updated.",
  "data": {
    "id": "mp_66f000000000000000000801",
    "title": "Australia vs New Zealand",
    "teamA": {
      "name": "Australia",
      "imageUrl": "https://cdn.example.com/teams/australia.png"
    },
    "teamB": {
      "name": "New Zealand",
      "imageUrl": "https://cdn.example.com/teams/new-zealand.png"
    },
    "predictionStartAt": "2026-10-01T08:00:00.000Z",
    "lockAt": "2026-10-10T19:25:00.000Z",
    "kickoffAt": "2026-10-10T19:30:00.000Z",
    "scoreBasis": "regulation",
    "viewPermission": {
      "roleIds": ["66f000000000000000000806"],
      "userIds": []
    },
    "rewards": {
      "correctResult": {
        "coins": 100,
        "xp": 50
      },
      "exactScoreBonus": {
        "coins": 200,
        "xp": 100
      }
    },
    "entryCost": {
      "enabled": false,
      "coins": 0
    },
    "status": "draft",
    "isPublished": false,
    "acceptingPredictions": false,
    "attemptCount": 0,
    "settlement": null,
    "version": 2,
    "createdAt": "2026-09-27T09:00:00.000Z",
    "updatedAt": "2026-09-28T10:00:00.000Z",
    "externalReference": "provider-fixture-123",
    "description": "Predict the final score."
  }
}
```

## 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. A `400` or `409` changes nothing.

### 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` | `version` is missing, the body changes nothing (`EMPTY_UPDATE`), a field breaks its rule or isn't recognised, the times you sent are out of order, `kickoffAt` isn't in the future, or a `viewPermission` role or user isn't in your community (`INVALID_REFERENCE`). `details` lists each problem. Nothing changed. |
| 400 | `READ_ONLY_FIELD` | The request has a field the server sets, such as `createdAt`, `isPublished` or `attemptCount`. `details` names it. Remove it. |
| 400 | `INVALID_JSON` | The body is not valid JSON. Fix the syntax and send it again. |
| 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 | `LOCKED_FIELDS_IMMUTABLE` | The body has `id`, `status` or `communityId`, which never change, or the match is no longer a draft and you sent `teamA`, `teamB`, `rewards`, `entryCost`, `scoreBasis` or `viewPermission`. Nothing changed. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `MATCH_PREDICTION_NOT_FOUND` | No match prediction in your community has this ID. Check it is the full `mp_...` ID from Create match prediction or List match predictions. |
| 404 | `COMMUNITY_NOT_FOUND` | The community this key belongs to no longer exists. Contact Returning.AI support. |
| 409 | `VERSION_CONFLICT` | The match changed after you read it. Read it again with Get match prediction, check your change still applies, and send it with the new `version`. |
| 409 | `SCHEDULE_MAY_NOT_MOVE_EARLIER` | The match is published and not yet settled, so its times can only move later. Send a time at or after the current one. |

### 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. This is also the response when `externalReference` is already used by another match. Read the match, and send the change again with its current `version` if it isn't there. |
| 500 | `AUTHENTICATION_FAILED` | The key could not be checked. Retry with backoff; nothing changed. |
| 503 | `MATCH_PREDICTION_FROZEN` | Returning.AI has paused match prediction requests. Nothing changed. Retry later with backoff. |

**Retries:** This endpoint doesn't take an `Idempotency-Key`, and the version check makes a blind retry safe: if the first request succeeded, the retry returns `409 VERSION_CONFLICT` and changes nothing. After a timeout or a `500`, read the match with Get match prediction. If your change is there, you're done; if not, send it again with the current `version`. 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

- [Publish match prediction](https://docs.returning.ai/api-reference/gamification-match-predictions/publish-match-prediction.md): `POST /v1/match-predictions/{matchPredictionId}/publish`. Publish the draft once it is ready.
- [Confirm the change with Get match prediction](https://docs.returning.ai/api-reference/gamification-match-predictions/get-match-prediction.md): `GET /v1/match-predictions/{matchPredictionId}`.
