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

# Redeeming vouchers

> Redeem one or several vouchers, attach a Redemption Entity, read the issued code or link, and handle every error safely.

A redemption takes one voucher from a voucher set in your catalog and hands it to you as a code or a link. The platform allocates vouchers first-expiring-first and never issues the same single-use voucher twice, also under heavy concurrency. Redeeming needs the permission `create-consumer-redemptions` (API keys can hold it); reading redemptions needs `view-consumer-redemptions`.

## Redeem a voucher

Send [`POST /consumer/v1/redemptions`](https://docs-consumer.voucher.allride.io/api-reference/consumer-redemptions-create) with an `Idempotency-Key`:

```bash theme={null}
curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemptions" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-8812:line-1" \
  -d '{
    "voucherSetId": 2030,
    "redemptionEntity": { "refId": "customer:42" },
    "metadata": { "orderId": "8812", "reason": "monthly-credit" }
  }'
```

The response is **201 Created**, sent only after the redemption is committed, with a `Location` header pointing at the new redemption:

```json theme={null}
{
  "data": {
    "id": 6017,
    "resource": "redemption",
    "status": "completed",
    "channel": "api",
    "providerPartnerId": 1030,
    "voucherSetId": 2030,
    "voucherSetRevisionNo": 2,
    "voucherId": 5017,
    "voucherSet": {
      "id": 2030,
      "name": { "de": "5 € Fahrguthaben", "en": "€5 ride credit" },
      "providerDisplayName": { "de": "Acme Scooters", "en": "Acme Scooters" }
    },
    "issued": {
      "kind": "code",
      "code": "ACME5-7KQ2-M9XA",
      "link": "https://acme-scooters.example.com/r?c=ACME5-7KQ2-M9XA"
    },
    "validFrom": null,
    "validUntil": "2026-12-31T22:59:59Z",
    "redemptionEntity": {
      "id": 9017,
      "resource": "redemption-entity",
      "refId": "customer:42",
      "_links": { "self": { "href": "https://api.voucher.allride.io/consumer/v1/redemption-entities/9017", "type": "application/json" } }
    },
    "redemptionLink": null,
    "batchId": null,
    "metadata": { "orderId": "8812", "reason": "monthly-credit" },
    "actor": { "type": "api-key", "id": 12017, "label": "Production integration" },
    "idempotencyKey": "order-8812:line-1",
    "legacyRef": null,
    "redeemedAt": "2026-10-21T17:42:10.120Z",
    "voidedAt": null,
    "voidedBy": null,
    "voidReason": null,
    "_links": {
      "self": { "href": "https://api.voucher.allride.io/consumer/v1/redemptions/6017", "type": "application/json" },
      "voucherSet": { "href": "https://api.voucher.allride.io/consumer/v1/catalog/voucher-sets/2030", "type": "application/json" },
      "redemptionEntity": { "href": "https://api.voucher.allride.io/consumer/v1/redemption-entities/9017", "type": "application/json" }
    }
  }
}
```

A voucher set is redeemable when it is visible in your [catalog](https://docs-consumer.voucher.allride.io/consumer/catalog.md), its availability is `active` or `deactivation-scheduled`, both Catalog Toggles are on, you have accepted its current [Price Terms](https://docs-consumer.voucher.allride.io/consumer/prices.md) and it has stock.

## Choosing a set or a provider

Name exactly one target:

* **`voucherSetId`**: redeem from this voucher set. Use it when your customer picked a specific offer.
* **`providerId`**: let the platform pick the provider's best redeemable set. Candidates are the provider's visible, switched-on sets in your effective `position` order (unset positions last), then by activation time. Sets without a redeemable Price Acceptance are skipped; a set that is depleted or whose entity limit is reached is skipped and the next one is tried.

Sending both, or neither, answers 400 [`data-failed-validation`](https://docs-consumer.voucher.allride.io/errors/catalog#data-failed-validation) with the constraint `mutually-exclusive` or `required`. In `providerId` mode, when every candidate is depleted you get 409 [`provider-depleted`](https://docs-consumer.voucher.allride.io/errors/catalog#provider-depleted); when every candidate was skipped for its entity limit, 409 [`entity-limit-reached`](https://docs-consumer.voucher.allride.io/errors/catalog#entity-limit-reached).

## Attaching a redemption entity

A [Redemption Entity](https://docs-consumer.voucher.allride.io/consumer/redemption-entities.md) is your record of who redeems: an end customer, an employee or a device, identified by your own opaque `refId`. Attach one in either of two ways (at most one per request):

* `redemptionEntityId`: an existing entity of yours.
* `redemptionEntity`: `{ "refId": "customer:42", "displayName": "…", "metadata": {} }`. The platform finds the entity with this `refId` or creates it in the same transaction. `metadata`, when sent and different, updates the entity.

Attaching an entity enables per-entity limits: a voucher set's `setting.entityLimit` (for example `{"max": 1, "period": "calendar-month"}`) caps how often one entity can redeem that set; the next attempt answers 409 [`entity-limit-reached`](https://docs-consumer.voucher.allride.io/errors/catalog#entity-limit-reached). A blocked entity answers 409 [`redemption-entity-blocked`](https://docs-consumer.voucher.allride.io/errors/catalog#redemption-entity-blocked), an erased one 409 [`redemption-entity-erased`](https://docs-consumer.voucher.allride.io/errors/catalog#redemption-entity-erased).

You can also send your own `metadata` (a JSON object up to 16 KiB). It is stored on the redemption and included in the `redemption-created` webhook. Never put personal data in `refId`, `metadata` or the `Idempotency-Key`: redemptions are kept for accounting after an entity is erased.

## Batch redemptions

[`POST /consumer/v1/redemptions:batch`](https://docs-consumer.voucher.allride.io/api-reference/consumer-redemptions-batch) redeems up to **10 vouchers** in one all-or-nothing request, for example a welcome bonus of three ride credits:

```bash theme={null}
curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemptions:batch" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: bonus:customer:42" \
  -d '{
    "items": [
      { "voucherSetId": 2030, "quantity": 2 },
      { "providerId": 1056, "quantity": 1 }
    ],
    "redemptionEntity": { "refId": "customer:42" },
    "metadata": { "reason": "new-customer-bonus" }
  }'
```

* `items` holds 1-10 items, each with `voucherSetId` or `providerId` and a `quantity` from 1 to 10. The sum of all quantities must not exceed 10, else 422 [`batch-too-large`](https://docs-consumer.voucher.allride.io/errors/catalog#batch-too-large).
* The entity and `metadata` apply to every redemption of the batch.
* The 201 response is a `redemption-batch` whose `items` are full redemptions in request order; an item with `quantity: 2` becomes two consecutive redemptions. Read it again with [`GET /consumer/v1/redemption-batches/{redemptionBatchId}`](https://docs-consumer.voucher.allride.io/api-reference/consumer-redemption-batches-get).
* If any item fails, nothing is redeemed. The error names the failing item in `more.validationErrors` with the property `items[<index>]`.
* Every redemption of the batch stores the batch's `Idempotency-Key`, so `GET /consumer/v1/redemptions?filter[idempotencyKey]=<key>` returns the whole batch.

> [!WARNING]
>
> Each item needs `quantity` **distinct** vouchers. A multi-use voucher (one shared code) counts once per batch, so `quantity: 2` on a set whose only stock is one shared code fails with 409 `voucher-set-depleted` and nothing is redeemed. For such sets send `quantity: 1`, or one batch per voucher. `stockStatus: available` never promises more than one voucher.

## What you receive

* **`issued`**: `kind` is `code` or `link`. For code sets, `code` holds the voucher code and `link` the link rendered from the set's Link Template (or `null` when the set has no Link Template); for link sets, `link` holds the link. Show it only to the entitled customer.
* **`validFrom` / `validUntil`**: when the voucher can be used at the provider. Allocation only picks vouchers that are still valid.
* **`voucherSet`**: the set's name and provider name as they were at redemption time, so historical redemptions stay readable after a set is renamed or ends.
* **`redemptionEntity`**, **`metadata`**, **`actor`**, **`idempotencyKey`** and **`redeemedAt`**.

Issued codes are not show-once. You can read them again at any time with [`GET /consumer/v1/redemptions/{redemptionId}`](https://docs-consumer.voucher.allride.io/api-reference/consumer-redemptions-get) or [list redemptions](https://docs-consumer.voucher.allride.io/api-reference/consumer-redemptions-list.md), for example filtered by `redemptionEntity.refId` or `redeemedAt`. Files downloaded from the dashboard show them masked.

You also receive the webhook [`redemption-created`](https://docs-consumer.voucher.allride.io/webhooks/events/redemption-created) with the same representation. The provider receives its own reduced projection that contains no consumer data, entity, metadata or issued value.

## Errors

Errors use the standard error body; branch on `errorCode`:

```json theme={null}
{
  "errors": [
    {
      "message": "You have not accepted the Price Terms in force for this voucher set.",
      "statusCode": 409,
      "errorCode": "price-acceptance-required",
      "more": { "voucherSetId": 2030, "priceAcceptanceStatus": "acceptance-required" }
    }
  ]
}
```

The first failing check answers; nothing is redeemed. The checks run in this order: set, redeemability, price, entity, entity limit, stock.

| Status | `errorCode` | Meaning | What to do |
| - | - | - | - |
| 404 | [`voucher-set-not-found`](https://docs-consumer.voucher.allride.io/errors/catalog#voucher-set-not-found) | The set is unknown or not visible to you | Refresh your catalog |
| 404 | [`provider-not-found`](https://docs-consumer.voucher.allride.io/errors/catalog#provider-not-found) | `providerId` has no set visible to you | Refresh your catalog |
| 404 | [`redemption-entity-not-found`](https://docs-consumer.voucher.allride.io/errors/catalog#redemption-entity-not-found) | `redemptionEntityId` is unknown | Check the id |
| 409 | [`voucher-set-unavailable`](https://docs-consumer.voucher.allride.io/errors/catalog#voucher-set-unavailable) | The set is not redeemable now: not active, provider inactive, access withdrawn or a toggle off | Hide the set; refresh your catalog |
| 409 | [`price-acceptance-required`](https://docs-consumer.voucher.allride.io/errors/catalog#price-acceptance-required) | You have not accepted the Price Terms in force, or the set has no price yet (`more.priceAcceptanceStatus` `acceptance-required` or `not-priced`) | Accept the prices on the Prices page, then retry with a **new** `Idempotency-Key` |
| 409 | [`voucher-set-depleted`](https://docs-consumer.voucher.allride.io/errors/catalog#voucher-set-depleted) | No claimable voucher is left | Offer another set, or redeem by `providerId` |
| 409 | [`provider-depleted`](https://docs-consumer.voucher.allride.io/errors/catalog#provider-depleted) | Every redeemable set of the provider is depleted | Offer another provider |
| 409 | [`entity-limit-reached`](https://docs-consumer.voucher.allride.io/errors/catalog#entity-limit-reached) | The entity reached the set's `entityLimit` | Tell the customer when the next period starts |
| 409 | [`redemption-entity-blocked`](https://docs-consumer.voucher.allride.io/errors/catalog#redemption-entity-blocked) | The entity is blocked | Unblock it if appropriate |
| 409 | [`redemption-entity-erased`](https://docs-consumer.voucher.allride.io/errors/catalog#redemption-entity-erased) | The entity was erased | Create a new entity |
| 400 | [`data-failed-validation`](https://docs-consumer.voucher.allride.io/errors/catalog#data-failed-validation) | The body is invalid; each problem is in `more.validationErrors` | Fix the request |
| 422 | [`batch-too-large`](https://docs-consumer.voucher.allride.io/errors/catalog#batch-too-large) | A batch asks for more than 10 vouchers | Split the batch |
| 500 | [`link-render-failed`](https://docs-consumer.voucher.allride.io/errors/catalog#link-render-failed) | The provider's link template could not be rendered; no voucher was consumed | Retry later; contact support with the `X-Request-Id` |
| 503 | [`db-unavailable`](https://docs-consumer.voucher.allride.io/errors/catalog#db-unavailable) | Temporary overload | Retry with the same `Idempotency-Key` after `Retry-After` |

A suspended partner gets 403 [`partner-suspended`](https://docs-consumer.voucher.allride.io/errors/catalog#partner-suspended). See [Errors](https://docs-consumer.voucher.allride.io/errors.md) for the common codes and the retry table.

## Best practices

* **Always send an `Idempotency-Key`** and reuse it for every retry of the same intent, for example `order-8812:line-1`. A retry after a timeout then replays the original 201 with the identical `issued` object instead of redeeming twice. See [Idempotency](https://docs-consumer.voucher.allride.io/guides/idempotency.md).
* **A stored 409 is final for its key.** After `voucher-set-depleted` or `price-acceptance-required`, a retry with the same key replays the 409 even if stock was added or prices were accepted. Use a new key for a new attempt.
* **Retry only transient failures** with the same key: network errors, 409 `idempotency-key-in-use`, 409 `concurrent-update` and 5xx.
* **Reconcile instead of guessing.** If you lost track of an intent, `GET /consumer/v1/redemptions?filter[idempotencyKey]=<key>`: a non-empty page means it ran.
* **Treat issued codes as secrets.** Show them only to the customer they belong to, never log them, and keep them out of analytics tools.
* **Keep your catalog fresh** with the `catalog-*` webhooks and hide entries whose `priceAcceptance.status` is not redeemable.
* **Sync your ledger incrementally** with `filter[redeemedAt][gte]=<last seen minus 60 seconds>&sort=redeemedAt` and de-duplicate by `id`.

## Related

* [Catalog and toggles](https://docs-consumer.voucher.allride.io/consumer/catalog.md)
* [Redemption entities](https://docs-consumer.voucher.allride.io/consumer/redemption-entities.md)
* [Voiding redemptions](https://docs-consumer.voucher.allride.io/consumer/voiding.md)
* [Idempotency](https://docs-consumer.voucher.allride.io/guides/idempotency.md)
* [Create a redemption](https://docs-consumer.voucher.allride.io/api-reference/consumer-redemptions-create.md)


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