# Get entity statistics Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-analytics-entity-stats /openapi/consumer.v1.json get /consumer/v1/analytics/entity-stats Unique and new redemption entities per period (zero-filled). Spans: at most 12 months or 31 days. Statistics use your partner time zone and are eventually consistent (up to 2 minutes). See [Analytics](/consumer/analytics). **Required permissions:** `view-consumer-analytics` · **API keys:** allowed # Get redemption link funnel statistics Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-analytics-link-stats /openapi/consumer.v1.json get /consumer/v1/analytics/link-stats Landing visits, OTP requests, verifications, redemptions and conversion per period, optionally per link. Spans: at most 92 days or 36 months. Statistics use your partner time zone and are eventually consistent (up to 2 minutes). See [Analytics](/consumer/analytics). **Required permissions:** `view-consumer-analytics` · **API keys:** allowed # Get the analytics overview Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-analytics-overview /openapi/consumer.v1.json get /consumer/v1/analytics/overview Returns key figures for a date range (default: this month so far) and the previous period of the same length. The range spans at most 366 days. Statistics use your partner time zone and are eventually consistent (up to 2 minutes). See [Analytics](/consumer/analytics). **Required permissions:** `view-consumer-analytics` · **API keys:** allowed # Get redemption statistics Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-analytics-redemption-stats /openapi/consumer.v1.json get /consumer/v1/analytics/redemption-stats Redemptions, voids and face value per period, by default per provider per month for the last 12 months. `groupBy` takes a comma list of `provider`, `voucher-set`, `channel`, `redemption-link`, or `none` (zero-filled periods). Spans: at most 36 months or 92 days. These are not invoice quantities: billing months follow Europe/Berlin and exclude imported redemptions. Statistics use your partner time zone and are eventually consistent (up to 2 minutes). See [Analytics](/consumer/analytics). **Required permissions:** `view-consumer-analytics` · **API keys:** allowed # Get the billing account Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-account-get /openapi/consumer.v1.json get /consumer/v1/billing/account Returns your billing account: legal name, address, VAT ID and its VIES status, invoice language, e-invoice format, payment preferences, credit balance, dunning standing and the `missingItems` that block invoicing. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Update the billing account Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-account-update /openapi/consumer.v1.json patch /consumer/v1/billing/account Changes your billing data (JSON Merge Patch). `mode`, `paymentTermsDays`, `voidedRedemptionsBillable` and `preferredPaymentMethodType` are set by AllRide (`400 data-failed-validation` with item `admin-only-field`): AllRide decides whether you pay by SEPA Direct Debit or by bank transfer. Changing `vatId` or `address.country` resets `vatIdStatus` to `unverified` and starts a VIES check. The account status is re-derived in the same transaction. Allowed while your partner is suspended, so you can fix billing data. See the [billing guide](/consumer/billing). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get the billing balance Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-balance-get /openapi/consumer.v1.json get /consumer/v1/billing/balance Returns open, overdue and credit amounts, the dunning standing and the next planned SEPA debit. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Add a billing contact Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-contacts-create /openapi/consumer.v1.json post /consumer/v1/billing/contacts Adds a billing contact. Needs a recent sign-in, because changing who receives invoices is a fraud path. The account status is re-derived (an `invoices` contact is required for invoicing). Allowed while your partner is suspended. See the [billing guide](/consumer/billing). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List billing contacts Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-contacts-list /openapi/consumer.v1.json get /consumer/v1/billing/contacts Lists the contacts that receive invoices, reminders and payment-failure emails. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Update a billing contact Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-contacts-update /openapi/consumer.v1.json patch /consumer/v1/billing/contacts/{billingContactId} Changes the name or roles of a billing contact (JSON Merge Patch). Needs a recent sign-in. Allowed while your partner is suspended. See the [billing guide](/consumer/billing). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Download an invoice document Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-invoices-document /openapi/consumer.v1.json get /consumer/v1/billing/invoices/{invoiceId}/document Returns a 15-minute signed URL for the invoice PDF or XML, with its SHA-256 and size. Use `format=xml` for machine processing. While the document is still rendering, the answer is 409 `invoice-document-not-ready` with `Retry-After: 30`. See [E-invoices](/consumer/billing#e-invoices-zugferd-xrechnung). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Get an invoice Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-invoices-get /openapi/consumer.v1.json get /consumer/v1/billing/invoices/{invoiceId} Returns one finalised invoice with totals, tax breakdown, the printed payment instructions and the current collection plan. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # List invoice lines Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-invoices-lines /openapi/consumer.v1.json get /consumer/v1/billing/invoices/{invoiceId}/lines Lists the lines of a finalised invoice: per provider, voucher set and price (or tier) the quantity, unit price and net amount. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # List invoices Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-invoices-list /openapi/consumer.v1.json get /consumer/v1/billing/invoices Lists your finalised invoices. Stornorechnungen are listed too (`kind: cancellation`, negative amounts). To fetch new invoices into your ERP, subscribe to `invoice-finalised`, or poll daily with `filter[issueDate][gte]=&sort=issueDate`. Drafts are never visible. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Resend an invoice Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-invoices-resend /openapi/consumer.v1.json post /consumer/v1/billing/invoices/{invoiceId}:resend Sends the stored invoice document again to the contacts with the `invoices` role (the document is never re-rendered). Allowed while your partner is suspended. See the [billing guide](/consumer/billing). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get a payment method Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-payment-methods-get /openapi/consumer.v1.json get /consumer/v1/billing/payment-methods/{paymentMethodId} Returns one SEPA Direct Debit mandate. Poll it after confirming the setup with Stripe.js to see `setup-pending` turn `active`. See [SEPA Direct Debit mandate](/consumer/billing#sepa-direct-debit-mandate). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # List payment methods Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-payment-methods-list /openapi/consumer.v1.json get /consumer/v1/billing/payment-methods Lists your SEPA Direct Debit mandates (masked: IBAN last 4 only): at most one `active` mandate and at most one setup in progress, plus `inactive` and `setup-failed` ones. Removed mandates are left out unless `filter[status]` asks for `removed`. See [SEPA Direct Debit mandate](/consumer/billing#sepa-direct-debit-mandate). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Make a payment method the default Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-payment-methods-make-default /openapi/consumer.v1.json post /consumer/v1/billing/payment-methods/{paymentMethodId}:make-default Makes an `active` SEPA Direct Debit mandate the default for collecting invoices. Since you have one active mandate at a time, which is the default, this changes nothing in practice. Allowed while your partner is suspended. See [SEPA Direct Debit mandate](/consumer/billing#sepa-direct-debit-mandate). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Remove a payment method Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-payment-methods-remove /openapi/consumer.v1.json post /consumer/v1/billing/payment-methods/{paymentMethodId}:remove Removes a SEPA Direct Debit mandate (status `removed`). An `active` or `inactive` mandate is detached at the payment provider, and open invoices without an active mandate are paid by bank transfer. A setup that did not succeed (`setup-pending` or `setup-failed`) is removed and its SetupIntent canceled; your active mandate is not affected. Removing a removed mandate again changes nothing. Needs a recent sign-in; not allowed while your partner is suspended. See [SEPA Direct Debit mandate](/consumer/billing#sepa-direct-debit-mandate). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Start a SEPA Direct Debit mandate setup Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-payment-methods-setup-sepa-debit /openapi/consumer.v1.json post /consumer/v1/billing/payment-methods:setup-sepa-debit Creates a payment method in `setup-pending` and a Stripe SetupIntent. No request body. Load Stripe.js with `publishableKey`, collect the IBAN in Stripe's IBAN Element together with the account holder's name and email, show the SEPA mandate text naming `creditor`, and confirm with `stripe.confirmSepaDebitSetup(clientSecret, …)`. The IBAN goes to Stripe only. The method stays `setup-pending` until Stripe confirms the mandate, then it is `active` and the default; poll it. You have one SEPA mandate at a time: starting a setup removes any other unfinished setup (its SetupIntent is canceled), and once the new mandate is `active` it replaces your previous one (`removed`, `deactivatedReasonClass: replaced`). Until then the previous mandate keeps collecting; if the new setup fails, it stays. AllRide decides whether you pay by SEPA Direct Debit or by bank transfer: while the billing account's `preferredPaymentMethodType` is `bank-transfer` this answers `409 sepa-not-enabled`. Dashboard only (the consumer dashboard's Billing › Payment method page, https://consumer.voucher.allride.io/go/billing-payment-method); needs a recent sign-in. Allowed while your partner is suspended. A replay with the same `Idempotency-Key` returns `clientSecret: null` and creates no second SetupIntent. See [SEPA Direct Debit mandate](/consumer/billing#sepa-direct-debit-mandate). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List payments Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-payments-list /openapi/consumer.v1.json get /consumer/v1/billing/payments Lists SEPA debits, bank transfers and manual payments on your invoices, including failed attempts and planned retries. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Get the usage estimate of a month Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-usage-get /openapi/consumer.v1.json get /consumer/v1/billing/usage Returns billable redemptions and estimated charges of a service month per provider and voucher set. Service months are calendar months in Europe/Berlin, so they can differ from analytics in your time zone at month edges. The figures are an estimate (`isEstimate: true`) until the month is invoiced. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # List usage lines of a month Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-usage-lines /openapi/consumer.v1.json get /consumer/v1/billing/usage/lines Pages the usage groups of one service month (the `lines` of the usage estimate) for reconciliation. `filter[serviceMonth]` is required (422 item `filter-required` otherwise), at most 13 months back. See the [billing guide](/consumer/billing). **Required permissions:** `view-consumer-billing` · **API keys:** allowed # Get a category Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-categories-get /openapi/consumer.v1.json get /consumer/v1/catalog/categories/{categoryId} Returns one active category. Archived or unknown categories answer 404 `category-not-found`. **Required permissions:** `view-consumer-catalog` · **API keys:** allowed # List categories Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-categories-list /openapi/consumer.v1.json get /consumer/v1/catalog/categories Lists the active categories managed by AllRide, ordered by `position`. **Required permissions:** `view-consumer-catalog` · **API keys:** allowed # Get a catalog provider Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-providers-get /openapi/consumer.v1.json get /consumer/v1/catalog/providers/{providerId} Returns one catalog provider with your setting. A provider without a voucher set visible to you answers 404 `provider-not-found`. See [Catalog and toggles](/consumer/catalog). **Required permissions:** `view-consumer-catalog` · **API keys:** allowed # List catalog providers Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-providers-list /openapi/consumer.v1.json get /consumer/v1/catalog/providers Lists the providers that have at least one voucher set visible to you, whatever your toggles say (management view). Apps should filter with `filter[providerToggle]=true`. Provider availability is `active` if any visible set is active, else `deactivation-scheduled`, else `upcoming`. See [Catalog and toggles](/consumer/catalog). **Required permissions:** `view-consumer-catalog` · **API keys:** allowed # Update provider toggle and settings Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-providers-update /openapi/consumer.v1.json patch /consumer/v1/catalog/providers/{providerId} Turns a provider on or off for you and edits your provider setting (JSON Merge Patch). The setting is created on the first save. Turning the provider off makes all its sets answer 409 `voucher-set-unavailable` on redeem. Changing `autoEnableNewVoucherSets` keeps the current toggle of every set already visible. See [Catalog and toggles](/consumer/catalog). **Required permissions:** `manage-consumer-catalog` · **API keys:** allowed # Get a catalog voucher set Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-voucher-sets-get /openapi/consumer.v1.json get /consumer/v1/catalog/voucher-sets/{voucherSetId} Returns one catalog entry with your setting. A voucher set that is not visible to you answers 404 `voucher-set-not-found`; a missing Price Acceptance never hides the entry, it shows in `priceAcceptance.status`. See [Catalog and toggles](/consumer/catalog). **Required permissions:** `view-consumer-catalog` · **API keys:** allowed # List catalog voucher sets Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-voucher-sets-list /openapi/consumer.v1.json get /consumer/v1/catalog/voucher-sets Lists the voucher sets visible to you: an approved live revision, effective status scheduled, active or deactivation-scheduled, an active provider, and access granted by AllRide. Entries are returned whatever your toggles say; apps and landing pages use `filter[providerToggle]=true&filter[voucherSetToggle]=true` and show only entries whose `priceAcceptance.status` is `accepted`, `change-pending` or `exempt`. Stock is shown only as `stockStatus`. Cache for at most 60 seconds and refresh on `catalog-*` webhooks. See [Catalog and toggles](/consumer/catalog). **Required permissions:** `view-consumer-catalog` · **API keys:** allowed # Update voucher set toggle and settings Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-catalog-voucher-sets-update /openapi/consumer.v1.json patch /consumer/v1/catalog/voucher-sets/{voucherSetId} Turns a voucher set on or off for you and edits your voucher set setting, including the per-entity limit `entityLimit`. A set is redeemable only when both its Provider Toggle and Voucher Set Toggle are on. Toggles may be set on upcoming sets. See [Catalog and toggles](/consumer/catalog). **Required permissions:** `manage-consumer-catalog` · **API keys:** allowed # Get email OTP readiness Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-email-otp-readiness-get /openapi/consumer.v1.json get /consumer/v1/email-otp-settings/readiness Tells link creators whether `email-otp` links can be created and what is missing, without SMTP or template details. Accepts no query parameters. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-redemption-links` · **API keys:** allowed # Get email OTP settings Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-email-otp-settings-get /openapi/consumer.v1.json get /consumer/v1/email-otp-settings Returns whether the Email OTP Gate is on and ready, and what is missing. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Update email OTP settings Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-email-otp-settings-update /openapi/consumer.v1.json patch /consumer/v1/email-otp-settings Turns the Email OTP Gate on (requires a verified SMTP configuration and an active template, else 422 `email-otp-not-configured`) or off, and sets how long a visitor stays signed in after verifying their email (`sessionDurationMinutes`, 15 to 1440). Both fields are optional. Turning the gate off keeps existing `email-otp` links active, but their landing pages stop sending new OTP emails. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed · **Recent sign-in:** required for dashboard users (15 minutes) # Get a redemption batch Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-batches-get /openapi/consumer.v1.json get /consumer/v1/redemption-batches/{redemptionBatchId} Returns a redemption batch with the current state of its redemptions (voided ones show `voided`). See [Redeeming vouchers](/consumer/redeeming). **Required permissions:** `view-consumer-redemptions` · **API keys:** allowed # Block a redemption entity Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-block /openapi/consumer.v1.json post /consumer/v1/redemption-entities/{redemptionEntityId}:block Blocks an active entity: redemptions for it answer 409 `redemption-entity-blocked`, and Redemption Links bound to it refuse it. Empty body. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `manage-consumer-redemption-entities` · **API keys:** allowed # Create a redemption entity Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-create /openapi/consumer.v1.json post /consumer/v1/redemption-entities Creates a redemption entity. `refId` is unique per partner; a second create with the same `refId` answers 409 `ref-id-taken` with the existing id. Entities created with an API key do not send the `redemption-entity-created` webhook. You can also create entities inline when redeeming. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `manage-consumer-redemption-entities` · **API keys:** allowed # Erase a redemption entity (GDPR) Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-erase /openapi/consumer.v1.json post /consumer/v1/redemption-entities/{redemptionEntityId}:erase Erases the entity's personal data synchronously and irreversibly: `refId`, `email`, `displayName` and `metadata` become `null` and the status `erased`. Its sessions are deleted and every `entity-bound` Redemption Link of the entity is revoked (the landing page then answers `redemption-link-revoked`). Redemptions keep the anonymous entity id and their own metadata. A new entity can later use the old `refId`. Empty body. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `erase-consumer-redemption-entities` · **API keys:** allowed # Get a redemption entity Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-get /openapi/consumer.v1.json get /consumer/v1/redemption-entities/{redemptionEntityId} Returns one redemption entity. Erased entities are returned anonymised. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `view-consumer-redemption-entities` · **API keys:** allowed # List redemption entities Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-list /openapi/consumer.v1.json get /consumer/v1/redemption-entities Lists your redemption entities. Metadata is not searchable. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `view-consumer-redemption-entities` · **API keys:** allowed # Unblock a redemption entity Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-unblock /openapi/consumer.v1.json post /consumer/v1/redemption-entities/{redemptionEntityId}:unblock Moves a blocked entity back to `active`. Empty body. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `manage-consumer-redemption-entities` · **API keys:** allowed # Update a redemption entity Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-entities-update /openapi/consumer.v1.json patch /consumer/v1/redemption-entities/{redemptionEntityId} Changes `refId`, `displayName` or `metadata` (JSON Merge Patch). Existing redemptions keep pointing to the entity id. See [Redemption entities](/consumer/redemption-entities). **Required permissions:** `manage-consumer-redemption-entities` · **API keys:** allowed # Create redemption links in bulk Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-batch /openapi/consumer.v1.json post /consumer/v1/redemption-links:batch Creates up to 100 links in one all-or-nothing call (for example one entity-bound link per employee). Any failing item creates nothing; item failures carry `more.validationErrors[].property` `items[]`. The response lists summaries; read full links with `GET`. See [Redemption links](/consumer/redemption-links). **Required permissions:** `manage-consumer-redemption-links` · **API keys:** allowed # Preview a link's catalog Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-catalog /openapi/consumer.v1.json get /consumer/v1/redemption-links/{redemptionLinkId}/catalog Shows what the link's landing page offers now, with the landing page's rules: active or deactivation-scheduled sets, both toggles on, inside the link's restrictions, not depleted, and with a redeemable Price Acceptance; at most 200 sets in landing order. `hiddenForPriceAcceptance` lists sets left out only because of a missing Price Acceptance. Accepts no query parameters. See [Redemption links](/consumer/redemption-links). **Required permissions:** `view-consumer-redemption-links`, `view-consumer-catalog` · **API keys:** allowed # Create a redemption link Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-create /openapi/consumer.v1.json post /consumer/v1/redemption-links Creates a Redemption Link. Your Workspace Branding must have `landing.privacyUrl` and `landing.imprintUrl`; an `email-otp` link needs the Email OTP Gate to be ready. Missing Price Acceptances do not block creation; such sets are left out of the landing page. A replay with the same `Idempotency-Key` returns the same link and token. See [Redemption links](/consumer/redemption-links). **Required permissions:** `manage-consumer-redemption-links` · **API keys:** allowed # Get a redemption link Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-get /openapi/consumer.v1.json get /consumer/v1/redemption-links/{redemptionLinkId} Returns one Redemption Link. See [Redemption links](/consumer/redemption-links). **Required permissions:** `view-consumer-redemption-links` · **API keys:** allowed # List redemption links Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-list /openapi/consumer.v1.json get /consumer/v1/redemption-links Lists your Redemption Links. `filter[status]=expired` includes active links past `expiresAt`. See [Redemption links](/consumer/redemption-links). **Required permissions:** `view-consumer-redemption-links` · **API keys:** allowed # Revoke a redemption link Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-revoke /openapi/consumer.v1.json post /consumer/v1/redemption-links/{redemptionLinkId}:revoke Revokes a link immediately and for good: the landing page answers `redemption-link-revoked` and open sessions stop working. Empty body. See [Redemption links](/consumer/redemption-links). **Required permissions:** `manage-consumer-redemption-links` · **API keys:** allowed # Update a redemption link Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemption-links-update /openapi/consumer.v1.json patch /consumer/v1/redemption-links/{redemptionLinkId} Changes a link (JSON Merge Patch). Raising the cap of an exhausted link, or moving `expiresAt` of an expired link into the future, makes it `active` again (this needs the landing legal URLs). Setting the cap equal to `redemptionCount` exhausts an active link and sends `redemption-link-exhausted`. Revoked links cannot change. See [Redemption links](/consumer/redemption-links). **Required permissions:** `manage-consumer-redemption-links` · **API keys:** allowed # Redeem several vouchers in one request Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemptions-batch /openapi/consumer.v1.json post /consumer/v1/redemptions:batch Redeems up to 10 vouchers in one all-or-nothing transaction. Each item needs `quantity` distinct claimable vouchers; a multi-use voucher counts once per batch, so `quantity` above 1 on a set whose only stock is one shared code fails with 409 `voucher-set-depleted`. A failing item rolls back the whole batch and `more.validationErrors[].property` is `items[]`. One `Idempotency-Key` covers the batch and is stored on every redemption. See [Redeeming vouchers](/consumer/redeeming). **Required permissions:** `create-consumer-redemptions` · **API keys:** allowed # Redeem a voucher Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemptions-create /openapi/consumer.v1.json post /consumer/v1/redemptions Allocates one voucher (first expiring first, never the same single-use voucher twice) from a voucher set (`voucherSetId`) or from the provider's best redeemable set (`providerId`) and returns the issued code or link. Optionally attaches a redemption entity by id or inline by `refId`, and stores your `metadata`. The response is sent only after the redemption is committed. Always send an `Idempotency-Key`: a replay returns the identical 201 including `issued`; a stored 409 is final for its key. Errors are checked in this order: unknown set (404), not redeemable (409 `voucher-set-unavailable`), no Price Acceptance (409 `price-acceptance-required`), entity problems, entity limit, stock. Sends the `redemption-created` webhook. See [Redeeming vouchers](/consumer/redeeming). **Required permissions:** `create-consumer-redemptions` · **API keys:** allowed # Get a redemption Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemptions-get /openapi/consumer.v1.json get /consumer/v1/redemptions/{redemptionId} Returns one redemption, including the issued code or link. See [Redeeming vouchers](/consumer/redeeming). **Required permissions:** `view-consumer-redemptions` · **API keys:** allowed # List redemptions Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemptions-list /openapi/consumer.v1.json get /consumer/v1/redemptions Lists your redemptions, including issued codes and links. For incremental sync use `filter[redeemedAt][gte]=&sort=redeemedAt` and de-duplicate by `id`. To check whether an intent already ran, filter by `idempotencyKey`. See [Redeeming vouchers](/consumer/redeeming). **Required permissions:** `view-consumer-redemptions` · **API keys:** allowed # Void a redemption Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-redemptions-void /openapi/consumer.v1.json post /consumer/v1/redemptions/{redemptionId}:void Voids a completed redemption within 30 days of `redeemedAt`. The voucher is never restocked; entity and link per-entity counters are decremented; an invoice already issued is not changed by the void. Sends the `redemption-voided` webhook and is always audited. See [Voiding redemptions](/consumer/voiding). **Required permissions:** `void-consumer-redemptions` · **API keys:** allowed # Get a template variable contract Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-template-contracts-get /openapi/consumer.v1.json get /partner/v1/email-template-contracts/{purpose} Returns the variables, helpers and limits of a template purpose. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Activate an email template Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-activate /openapi/consumer.v1.json post /partner/v1/email-templates/{emailTemplateId}:activate Activates a valid draft (no missing required or unknown variables, no blocking finding, renders with sample data) and archives the previously active template; the next OTP email uses it. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Archive an email template Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-archive /openapi/consumer.v1.json post /partner/v1/email-templates/{emailTemplateId}:archive Archives a draft, or the active template while the Email OTP Gate is off. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Create an email template Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-create /openapi/consumer.v1.json post /partner/v1/email-templates Creates a draft template, either uploaded (`subject`, `htmlBody`, …) or generated from your Workspace Branding (`generateFromBranding: true`). Validation runs immediately and is stored in `validation`; an invalid draft is still saved. Only forbidden constructs and oversized sources are refused with 422 `template-invalid`. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Get an email template Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-get /openapi/consumer.v1.json get /partner/v1/email-templates/{emailTemplateId} Returns one template with its bodies. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # List email templates Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-list /openapi/consumer.v1.json get /partner/v1/email-templates Lists your email templates without their bodies; read one template for the bodies. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Render a saved template preview Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-preview /openapi/consumer.v1.json get /partner/v1/email-templates/{emailTemplateId}/preview Renders subject, HTML and text with sample data. A locale missing in the template falls back to your default locale (`fallbackLocaleUsed`). Display `html` only in a sandboxed iframe. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Render an unsaved template preview Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-preview-draft /openapi/consumer.v1.json post /partner/v1/email-templates:preview Renders and validates a template without saving it. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Update a draft email template Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-email-templates-update /openapi/consumer.v1.json patch /partner/v1/email-templates/{emailTemplateId} Edits a draft template; validation is recomputed. Active and archived templates cannot change: create a new draft and activate it. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Get the SMTP configuration Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-smtp-configuration-get /openapi/consumer.v1.json get /partner/v1/smtp-configuration Returns your SMTP configuration (never the password). Poll it after `:verify` until `verificationCompletedAt` ≥ `verificationRequestedAt`. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Create or replace the SMTP configuration Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-smtp-configuration-replace /openapi/consumer.v1.json put /partner/v1/smtp-configuration Creates (201) or replaces (200) your SMTP configuration. Every save sets `status` to `unverified`; existing `email-otp` links keep sending with the new settings, but new `email-otp` links need a successful `:verify`. Hosts resolving to private or reserved addresses are refused. `Idempotency-Key` is ignored. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed · **Recent sign-in:** required for dashboard users (15 minutes) # Verify SMTP and send a test email Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-smtp-configuration-verify /openapi/consumer.v1.json post /partner/v1/smtp-configuration:verify Starts an asynchronous verification that sends a test email: to the requesting user, or for API keys to your partner's `contacts.technicalEmail` (never to an address from the request). Answers 200 with `verificationRequestedAt` set; poll `GET /partner/v1/smtp-configuration` until `verificationCompletedAt` ≥ `verificationRequestedAt`, then read `status` and `lastError`. One verification per partner at a time. See [Email OTP links](/consumer/email-otp-links). **Required permissions:** `manage-consumer-email` · **API keys:** allowed # Analytics Source: https://allride-voucher-consumer.mintlify.app/consumer/analytics Redemption, entity and link statistics in your time zone, and how they differ from billing quantities. The Analytics endpoints answer "how many vouchers did we redeem, from whom, through which channel, and how well do our links convert". They need `view-consumer-analytics`, which you can delegate to an API key. ## Statistics | Operation | What it returns | | - | - | | [Analytics overview](/api-reference/consumer-analytics-overview) `GET /consumer/v1/analytics/overview` | One summary for a date range (`from`, `to`, at most 366 days; default: this month so far): redeemed, voided and net counts with the void rate, the same figures for the previous period of equal length, unique and new redemption entities, face value issued in euros, redemptions per channel, your top 5 providers, the Redemption Link funnel and your catalog coverage (visible versus enabled providers and voucher sets, upcoming and leaving sets, enabled sets that are low or depleted) | | [Redemption statistics](/api-reference/consumer-analytics-redemption-stats) `GET /consumer/v1/analytics/redemption-stats` | Rows per period (`granularity=month` or `day`), grouped by `provider` (default), `voucher-set`, `channel`, `redemption-link` (comma-separated) or `none` | | [Entity statistics](/api-reference/consumer-analytics-entity-stats) `GET /consumer/v1/analytics/entity-stats` | Per period: unique redemption entities that redeemed, new entities, and redemptions without an entity | | [Link funnel statistics](/api-reference/consumer-analytics-link-stats) `GET /consumer/v1/analytics/link-stats` | Per period, optionally per link: landing visits, OTP emails requested, OTPs verified, redemptions and the conversion rate | Redemptions per provider per month, the headline figure, is the default of the redemption statistics: ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/consumer/v1/analytics/redemption-stats" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "granularity=month" \ --data-urlencode "groupBy=provider" \ --data-urlencode "filter[period][gte]=2026-09" \ --data-urlencode "filter[period][lte]=2026-10" ``` ```json theme={null} { "data": { "data": [ { "resource": "redemption-stats-row", "period": "2026-10", "periodStart": "2026-09-30T22:00:00Z", "periodEnd": "2026-10-31T23:00:00Z", "providerPartnerId": 1030, "providerDisplayName": { "de": "Acme Scooters", "en": "Acme Scooters" }, "voucherSetId": null, "voucherSetName": null, "channel": null, "redemptionLinkId": null, "redeemedCount": 30117, "voidedCount": 70, "netCount": 30047, "faceValue": { "value": 15058500, "label": "€150,585.00" } } ], "page": 1, "perPage": 25, "totalPages": 1, "total": 1 } } ``` How to read the rows: * Statistics rows are values, not resources: they have `resource` but no `id` or `_links`. Dimensions you did not group by are `null`. * A void counts in the period of its `voidedAt`, not of the original redemption. `netCount` = redeemed − voided, so a period can have a negative `netCount`. * With `groupBy=none` every period of the range is returned, zero-filled; with a grouping, only combinations that have data. Entity statistics are always zero-filled. * Filter with `filter[period][gte]`, `filter[period][lte]` (months `YYYY-MM` or days `YYYY-MM-DD`), `filter[providerPartnerId]`, `filter[voucherSetId]`, `filter[channel]` and `filter[redemptionLinkId]`. The default range is the last 12 months, or the last 30 days for daily rows. Ranges are limited (redemption and link statistics: 36 months or 92 days; entity statistics: 12 months or 31 days); a longer range fails with `400 data-failed-validation`: the item has `property` `filter[period][gte]`, the constraint [`invalid-filter`](/errors/catalog#invalid-filter) and `params.maxMonths` (36, or 12 for entity statistics) or `params.maxDays` (92, or 31 for entity statistics). * `faceValue` sums the face value of the redeemed vouchers in euros; sets without a face value contribute nothing. * The conversion rate of the link funnel is redemptions ÷ visits × 100, `null` when there were no visits. Statistics are eventually consistent: a redemption appears within about 2 minutes. The authoritative record of every single redemption is [List redemptions](/api-reference/consumer-redemptions-list). ## Time zones Periods are calendar months and days in **your partner time zone** (`timezone` of your partner, for example `Europe/Berlin`). Each row carries `periodStart` and `periodEnd`, the period's half-open bounds in UTC, and the overview echoes the `timezone` it used. Internally AllRide counts in hourly UTC buckets and rolls them up into your time zone when you query, so changing your time zone changes how existing data is grouped. > [!WARNING] > > Monthly statistics are not your invoice quantities. Billing months always follow Berlin time (`Europe/Berlin`), whatever your partner time zone; billing excludes imported legacy redemptions, and a void after invoicing does not change the issued invoice. A redemption at `2026-10-31T23:30:00Z` counts in October for a partner in `Europe/London`, but in the November billing month. For billing quantities use the [usage estimate](/consumer/billing#usage-and-pricing). For raw rows rather than statistics, page through [List redemptions](/api-reference/consumer-redemptions-list) as described in [Downloading large lists](/guides/pagination#downloading-large-lists). ## Related * [Pagination, filtering and sorting](/guides/pagination) * [Billing](/consumer/billing) * [Redemption links](/consumer/redemption-links) * [Pagination, filtering and sorting](/guides/pagination) # Billing Source: https://allride-voucher-consumer.mintlify.app/consumer/billing How AllRide bills you per redemption: billing account, SEPA Direct Debit, invoices and Stornorechnungen, ZUGFeRD and XRechnung, usage, payments, dunning, billing webhooks and permissions. This guide is for Partner Consumers. AllRide bills only consumers; providers are never billed and never see your billing data. Everything here is available in the consumer dashboard under **Billing** and through the `/consumer/v1/billing` endpoints of the [API reference](/api-reference/consumer-billing-account-get). ## How you are billed * AllRide bills you **in arrears, once a month, per billable redemption**. There is no deposit, no advance payment and no subscription fee. * A **billable redemption** is a completed redemption of yours, whatever the channel (API, dashboard or Redemption Link). Imported legacy redemptions are not billed. * The **service month** of a redemption is the calendar month of its `redeemedAt` in **Berlin time (`Europe/Berlin`)**, whatever your partner time zone. Billing months follow Berlin time, so your [analytics](/consumer/analytics) in another time zone can differ from the invoice at the month edges. * On the 1st of each month at 06:00 Berlin time AllRide drafts the invoice for the previous month. AllRide reviews every draft before it is issued, so the invoice arrives a little later. * **Voided redemptions are not billed.** Void before the month is invoiced and the redemption never appears on an invoice. An issued invoice is never changed afterwards, so a void after invoicing does not reduce it; if an invoice is wrong, talk to AllRide (see [Corrections: Stornorechnung](#corrections-stornorechnung)). * A month with usage always produces an invoice, even if its total is zero (for example when every provider you used is free); such an invoice is immediately `paid`. A month without usage produces no invoice. * The price per redemption is set by AllRide for you, possibly per provider or voucher set, and you accept it before you can redeem: see [Prices and price acceptance](/consumer/prices). * **AllRide decides how you pay**: by SEPA Direct Debit (the default; you set up the mandate yourself, see [SEPA Direct Debit mandate](#sepa-direct-debit-mandate)) or by bank transfer against each invoice. You see the choice as `preferredPaymentMethodType` on your billing account. ```mermaid theme={null} flowchart LR R[Redemptions in month M, Berlin time] --> D[Draft on the 1st of M+1, 06:00] D --> A[Review by AllRide] A --> F[Invoice issued: number, PDF and XML] F --> E[Email to your invoice contacts] E --> P[SEPA debit or bank transfer] ``` ## Billing account and contacts Your partner has one **billing account**, created automatically when you become a consumer. > [!NOTE] > > **Coming soon.** Changing the billing account through `PATCH /consumer/v1/billing/account` is not available yet. Until it is, your AllRide account manager updates the account for you; reading the account and managing contacts work today. * Read it with `GET /consumer/v1/billing/account` ([Get the billing account](/api-reference/consumer-billing-account-get)). * Change it with `PATCH /consumer/v1/billing/account` ([Update the billing account](/api-reference/consumer-billing-account-update)), a JSON merge patch. You can change `legalName`, `address`, `vatId` (`null` clears it), `buyerReference` (your Leitweg-ID for XRechnung), `purchaseOrderReference` (printed as your PO number), `invoiceLanguage` (`de` or `en`) and `eInvoiceFormat`. * **AllRide chooses how you pay.** `preferredPaymentMethodType` is `sepa-debit` (SEPA Direct Debit, the default) or `bank-transfer` (you pay each invoice by bank transfer). It is set by AllRide and read-only for you; talk to your AllRide account manager if you would like to switch. Rules: * Fields that only AllRide sets, such as `mode`, `paymentTermsDays`, `voidedRedemptionsBillable` and `preferredPaymentMethodType`, are refused with `data-failed-validation` and the constraint `admin-only-field`. * The first two letters of `vatId` must match the country of your address (Greece: `EL`), else the constraint is `vat-id-country-mismatch`. * AllRide checks EU VAT IDs against VIES. Changing `vatId` or the address country resets `vatIdStatus` to `unverified` until the new check completes. * `missingItems` lists what blocks invoicing: `legal-name`, `address`, `invoice-contact`, `vat-id` (for EU customers outside Germany a valid VAT ID is required) and `buyer-reference` (required for XRechnung). Your account is `active` when the list is empty and `incomplete` otherwise; it switches back and forth automatically. An incomplete account still records usage, but AllRide cannot issue its invoice until you complete it. **Contacts** decide who receives billing emails. Each contact has an email, an optional name and at least one role: `invoices` (invoice and Stornorechnung emails, including the SEPA pre-notification), `dunning` (reminders and dunning notices) and `payment-failures` (failed debits). | Operation | Endpoint | | - | - | | [List billing contacts](/api-reference/consumer-billing-contacts-list) | `GET /consumer/v1/billing/contacts` | | [Add a billing contact](/api-reference/consumer-billing-contacts-create) | `POST /consumer/v1/billing/contacts` | | [Update a billing contact](/api-reference/consumer-billing-contacts-update) | `PATCH /consumer/v1/billing/contacts/{billingContactId}` (name and roles; to change the email, remove and add) | | [Remove a billing contact](/api-reference/consumer-billing-contacts-delete) | `DELETE /consumer/v1/billing/contacts/{billingContactId}` | An email that is already a contact answers 409 [`billing-contact-exists`](/errors/catalog#billing-contact-exists); a contact without a role fails with the constraint `billing-contact-role-required`. At least one contact with the role `invoices` is required for invoicing. Changing the account or its contacts requires a sign-in within the last 15 minutes, because these settings decide where invoices go. Billing writes stay possible while your partner is suspended, so you can fix payment details. They are dashboard-only: an API key cannot hold `manage-consumer-billing`. ## SEPA Direct Debit mandate > [!NOTE] > > **Coming soon.** SEPA Direct Debit and the payment-method operations of this section are not available yet. AllRide switches them on after sign-off and announces it by email to your technical contact (`contacts.technicalEmail`). Until then, pay your invoices by bank transfer as printed on each invoice. You set up a SEPA Direct Debit mandate when AllRide has chosen SEPA Direct Debit for you (`preferredPaymentMethodType` is `sepa-debit`). The mandate is yours to sign, so you set it up yourself **in the consumer dashboard**, under [Billing › Payment method](https://consumer.voucher.allride.io/go/billing-payment-method). The operation behind it, `POST /consumer/v1/billing/payment-methods:setup-sepa-debit` ([Set up SEPA Direct Debit](/api-reference/consumer-billing-payment-methods-setup-sepa-debit)), needs `manage-consumer-billing` and a recent sign-in, so API keys cannot call it. If AllRide collects your invoices by bank transfer, it answers 409 [`sepa-not-enabled`](/errors/catalog#sepa-not-enabled). 1. A member with `manage-consumer-billing` starts the setup. The request has no body. The response holds the new payment method (`setup-pending`), the Stripe `clientSecret` (shown once), Stripe's `publishableKey` and the `creditor` (AllRide's name and SEPA creditor ID). 2. The dashboard loads Stripe.js with `publishableKey` and shows Stripe's IBAN field together with the account holder's name and email and the SEPA mandate text naming the creditor. The IBAN goes straight to Stripe: AllRide never receives the full IBAN and stores only the last four digits (`ibanLast4`), the bank code and the bank country. 3. You accept the mandate, and the dashboard confirms it with Stripe: ```js theme={null} const stripe = await loadStripe(setup.publishableKey); const iban = stripe.elements().create("iban", { supportedCountries: ["SEPA"] }); iban.mount("#iban"); // after the user accepted the mandate text await stripe.confirmSepaDebitSetup(setup.clientSecret, { payment_method: { sepa_debit: iban, billing_details: { name: "Example Transit GmbH", email: "billing@example.com" }, }, }); ``` 4. The payment method stays `setup-pending` until Stripe confirms the mandate, usually within a minute, then it is `active` and becomes your default. The dashboard polls [Get a payment method](/api-reference/consumer-billing-payment-methods-get) until then. A setup that is not confirmed within 24 hours (`setupExpiresAt`) ends `setup-failed`; remove it or start a new one. **One mandate at a time.** You have one SEPA mandate. To change the bank account, start a new setup (the dashboard's button reads "Replace mandate"): * Starting it removes any other unfinished setup and cancels its SetupIntent. * Your current mandate keeps collecting until Stripe confirms the new one. Then the new mandate is `active` and the default, and the old one is `removed` with `deactivatedReasonClass: replaced`. Scheduled debits move to the new mandate, with a new pre-notification. The webhook [`payment-method-deactivated`](/webhooks/events/payment-method-deactivated) reports the old one with `reasonClass: replaced`, and the activation email names the IBAN it replaced. * If the new setup fails, your current mandate stays. Read your payment methods with [List payment methods](/api-reference/consumer-billing-payment-methods-list) (`GET /consumer/v1/billing/payment-methods`, removed mandates left out unless `filter[status]` asks for them) and [Get a payment method](/api-reference/consumer-billing-payment-methods-get). In the dashboard you can remove the mandate or an unfinished setup (`:remove`); removing a setup that did not succeed cancels it and does not touch your active mandate. **Pre-notification and collection date.** The invoice email is the SEPA pre-notification: it states the amount, the collection date, the mandate reference, AllRide's creditor ID and the last four digits of your IBAN. AllRide collects on the due date, but never earlier than 3 days after the email was sent, moved to the next TARGET2 business day. **Failed debits.** Every failed debit sends the webhook [`payment-failed`](/webhooks/events/payment-failed) and an email to contacts with the role `payment-failures`. * **Soft failures** (for example insufficient funds) are retried twice, 7 and 14 days after the failure is reported, within 30 days of the first failure. Each retry comes with a new pre-notification. * **Hard failures** (mandate revoked, account closed, account details wrong) deactivate the mandate ([`payment-method-deactivated`](/webhooks/events/payment-method-deactivated)) and move the invoice to bank transfer; your contacts receive the bank-transfer instructions. Set up a new mandate to return to direct debit. Invoices above the SEPA limit (€10,000 by default) are paid by bank transfer. ## Invoices and Stornorechnungen > [!NOTE] > > **Coming soon.** AllRide is reviewing the first billing months internally before it issues invoices. Until the first invoice is issued, the invoice list is empty and no `invoice-*` webhook is sent; your [usage estimate](#usage-and-pricing) already shows what you will be billed. | Operation | Endpoint | | - | - | | [List invoices](/api-reference/consumer-billing-invoices-list) | `GET /consumer/v1/billing/invoices` | | [Get an invoice](/api-reference/consumer-billing-invoices-get) | `GET /consumer/v1/billing/invoices/{invoiceId}` | | [List invoice lines](/api-reference/consumer-billing-invoices-lines) | `GET /consumer/v1/billing/invoices/{invoiceId}/lines` | | [Download an invoice document](/api-reference/consumer-billing-invoices-document) | `GET /consumer/v1/billing/invoices/{invoiceId}/document?format=pdf` or `format=xml` | | [Resend an invoice](/api-reference/consumer-billing-invoices-resend) (dashboard) | `POST /consumer/v1/billing/invoices/{invoiceId}:resend` | * You see only issued documents. Drafts in review are invisible (404 [`invoice-not-found`](/errors/catalog#invoice-not-found)). * Numbers are gapless per year in one series, for example `VP-R-2026-000042`. Stornorechnungen use the same series. * `kind` tells you what an invoice is: `periodic` (the monthly invoice), `final` (after offboarding), `corrective` (a corrected invoice issued after a cancellation) or `cancellation` (a Stornorechnung, see below). * Invoice statuses: `finalised` (issued, email not yet sent), `sent`, `partially-paid`, `paid`, `overdue`, `uncollectible` and `void` (cancelled by a Stornorechnung). * Each invoice line shows the provider, voucher set, quantity, `unitPrice` and net amount. Amounts are `Money` in cents; tax rates are decimal strings such as `"19.00"`. ### Corrections: Stornorechnung An issued invoice is never edited or deleted (GoBD). If an invoice is wrong, AllRide cancels it with a **Stornorechnung** (cancellation invoice) and then issues a corrected invoice in the usual way: 1. The Stornorechnung is an invoice of its own with `kind: cancellation`, a new number in the same series and `cancelsInvoiceId` pointing to the cancelled invoice. Its document refers to the original invoice number and date. 2. It mirrors the original lines with **negative amounts**: each line has a negative `quantity` and `netAmount` with the original, positive `unitPrice`; `netAmount`, `taxAmount`, `grossAmount` and the tax breakdown are negative. Nothing is due on it: `amountDue` is `0`, `paymentInstructions.type` is `settled` and it stays `finalised` or `sent`. 3. The original invoice becomes `void`, with `cancelledByInvoiceId` naming the Stornorechnung and `amountCancelled` equal to its gross amount. Open SEPA debits or bank transfer requests for it are cancelled, and anything you already paid on it moves to your credit balance, which AllRide applies to your next invoices. 4. Where you should be billed again, AllRide issues a corrected invoice (`kind: corrective`) that you pay as usual. Both documents reach you like any other invoice: by email to your `invoices` contacts, in the invoice list and through the [`invoice-finalised`](/webhooks/events/invoice-finalised) webhook. The cancelled invoice also sends [`invoice-voided`](/webhooks/events/invoice-voided). In your bookkeeping, post the Stornorechnung against the original invoice; together they net to zero. **Downloads.** The document endpoint returns a short-lived link, not the file: ```json theme={null} { "data": { "resource": "document-download", "format": "xml", "url": "https://storage.googleapis.com/allride-voucher-billing/VP-R-2026-000042.xml?X-Goog-Signature=example", "expiresAt": "2026-11-02T08:20:00Z", "sha256": "9f2c1b7e4d3a5c6b8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "sizeBytes": 48211, "fileName": "VP-R-2026-000042.xml" } } ``` The `url` is valid for **15 minutes**: download immediately and do not store the URL; ask again when you need the file later. Compare the file's SHA-256 with `sha256`. A document that is still being rendered answers 409 [`invoice-document-not-ready`](/errors/catalog#invoice-document-not-ready) with `Retry-After: 30`. ### Fetching invoices into your ERP with an API key 1. Create an API key with the scope `view-consumer-billing` in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/api-keys). 2. Subscribe a webhook endpoint to [`invoice-finalised`](/webhooks/events/invoice-finalised), which is sent once the document exists (for Stornorechnungen too), and to [`invoice-voided`](/webhooks/events/invoice-voided) to mark cancelled invoices. 3. On each webhook, request the document (`format=xml` for machine processing), download it and verify the hash: ```bash theme={null} INVOICE_ID=19017 DOWNLOAD=$(curl -s "$VOUCHER_API_BASE_URL/consumer/v1/billing/invoices/$INVOICE_ID/document?format=xml" \ -H "Authorization: Bearer $VOUCHER_API_KEY") DOCUMENT_URL=$(echo "$DOWNLOAD" | jq -r '.data.url') EXPECTED_SHA256=$(echo "$DOWNLOAD" | jq -r '.data.sha256') curl -s -o invoice.xml "$DOCUMENT_URL" echo "$EXPECTED_SHA256 invoice.xml" | shasum -a 256 -c - ``` 4. As a fallback for missed webhooks, poll daily for invoices issued since your last run: ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/consumer/v1/billing/invoices" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "filter[issueDate][gte]=2026-11-01" \ --data-urlencode "sort=issueDate" ``` Stornorechnungen come through the same endpoints; tell them apart by `kind: cancellation` and match them to the original through `cancelsInvoiceId`. ## E-invoices (ZUGFeRD/XRechnung) > [!NOTE] > > **Coming soon.** E-invoices arrive with the first issued invoice (see [Invoices and Stornorechnungen](#invoices-and-stornorechnungen)). Make sure your billing account has the right `eInvoiceFormat` and, for XRechnung, `buyerReference` (your Leitweg-ID) beforehand, so the first invoice is issued in the right format. Every invoice, Stornorechnungen included, is an e-invoice according to EN 16931. You choose the format with `eInvoiceFormat` on your billing account: | `eInvoiceFormat` | What you get | | - | - | | `zugferd-en16931` (default) | A PDF/A-3 with the embedded EN 16931 CII XML (`factur-x.xml`), ZUGFeRD profile EN 16931. `format=xml` returns the same XML as a separate file | | `xrechnung-cii` | For public-sector buyers: XRechnung in CII syntax. The XML is the legal original and the PDF a visualisation; the invoice email attaches both | XRechnung requires your Leitweg-ID in `buyerReference` (BT-10). Without it your account is incomplete and invoicing is blocked. Every document is validated (XML schema, Schematron, PDF/A) before it is issued. Documents are in your `invoiceLanguage`. ## Usage and pricing `GET /consumer/v1/billing/usage?serviceMonth=2026-11` ([Get the usage estimate](/api-reference/consumer-billing-usage-get)) shows what you have used in a Berlin service month (default: the current month; up to 13 months back), per provider and voucher set: `quantity`, `voidedQuantity`, `unitPrice`, `netAmount` and `priced`, plus totals and `asOf`. * Until the month is invoiced the response is an **estimate** (`isEstimate: true`), updated within about 2 minutes of each redemption. Once invoiced, `invoiced: true` and the amounts come from the invoice. * `priced: false` means the price is still to be confirmed; such lines have no amounts. * `unitPrice` is a `UnitPrice`: `{value, scale: 4, label}` with `value` in ten-thousandths of a euro, so `1000` is €0.10 and `1250` is €0.1250. Totals are `Money` in cents. * Unit prices are set by AllRide for you and may differ per provider and voucher set; a price of zero is possible. You see only the resolved price that applies to you, never how it was derived. The Price Terms per voucher set and your acceptance of them are in [Prices and price acceptance](/consumer/prices). For reconciliation in your ERP, `GET /consumer/v1/billing/usage/lines?filter[serviceMonth]=2026-11` ([List usage lines](/api-reference/consumer-billing-usage-lines)) pages the same lines; `filter[serviceMonth]` is required. `GET /consumer/v1/billing/balance` ([Get the balance](/api-reference/consumer-billing-balance-get)) shows your open, overdue and credit amounts, your `standing` and the next planned collection. For files, page through [List invoices](/api-reference/consumer-billing-invoices-list) or the usage lines of one service month as described in [Downloading large lists](/guides/pagination#downloading-large-lists). ## Payments and dunning > [!NOTE] > > **Coming soon.** The payment list, the balance and the dunning emails start with the first issued invoice. The operations below are documented ahead of their release. `GET /consumer/v1/billing/payments` ([List payments](/api-reference/consumer-billing-payments-list)) lists every payment attempt with its method (`sepa-debit`, `bank-transfer` or `manual`), status, amount and, for failures, the failure class and the next attempt date. **Bank transfer.** Invoices are paid by bank transfer when AllRide has chosen bank transfer for you (`preferredPaymentMethodType` is `bank-transfer`), when you have no active mandate, and when they are above the SEPA limit. Pay to the account in `bankTransferInstructions` of your billing account (also printed on the invoice) and use **exactly the reference printed on the invoice**, so the payment is matched automatically. AllRide applies transfers it cannot match by hand. Payment terms are 14 days unless agreed otherwise (`paymentTermsDays`). **Dunning** (days after the due date, Berlin time): | Day | Step | Email to contacts with role `dunning` | | - | - | - | | +3 | Payment reminder | Yes | | +14 | First dunning notice | Yes | | +28 | Second and final dunning notice | Yes | | +42 | Account flagged `delinquent` | No; AllRide contacts you | Your billing account and balance show the current level as `standing` (`good-standing`, `reminded`, `dunning-1`, `dunning-2`, `delinquent`). No fees or interest are added automatically, and AllRide never stops your redemptions automatically for non-payment. Emails are sent on business days only. ## Billing webhooks These webhook types are only for consumers. Subscribing an endpoint to them requires `view-consumer-billing`. The `payload` starts with `resource` and `id`, is the consumer view of the resource and never contains payment-provider messages. | Type | Sent when | `payload` | | - | - | - | | [`invoice-finalised`](/webhooks/events/invoice-finalised) | An invoice, also a Stornorechnung, is issued and its document is ready to download | `Invoice` | | [`invoice-paid`](/webhooks/events/invoice-paid) | An invoice is fully paid | `Invoice` | | [`invoice-overdue`](/webhooks/events/invoice-overdue) | An invoice passes its due date unpaid | `Invoice` | | [`invoice-voided`](/webhooks/events/invoice-voided) | AllRide cancels an invoice with a Stornorechnung | `Invoice` with status `void`, plus `cancellationInvoiceId`, the Stornorechnung (also in `cancelledByInvoiceId`) | | [`payment-failed`](/webhooks/events/payment-failed) | A payment attempt fails | The payment (`resource: payment`) with `invoiceId`, `failureClass` and `nextAttemptDate` | | [`payment-method-deactivated`](/webhooks/events/payment-method-deactivated) | A mandate becomes inactive | The mandate (`resource: payment-method`) with `ibanLast4` and `reasonClass` | Deliveries follow the general [webhook rules](/webhooks/overview): at least once, unordered, signed. ## Permissions | Permission | Grants | API keys | | - | - | - | | `view-consumer-billing` | Read the billing account, contacts, payment methods (masked), invoices (Stornorechnungen included), payments, usage and balance; download documents; subscribe to billing webhooks | Yes (delegable) | | `manage-consumer-billing` | Edit the billing account and contacts, set up and remove mandates, resend documents | No: dashboard only. Creating an API key with this scope fails with the constraint `scope-not-delegable` | The default groups `owners`, `administrators` and `billing` hold both permissions. The groups `developers` and `viewers` do not read billing data. ## Related * [Prices and price acceptance](/consumer/prices) * [Analytics](/consumer/analytics) * [Pagination, filtering and sorting](/guides/pagination) * [Webhooks](/webhooks/overview) * [Error catalog](/errors/catalog) # Catalog and toggles Source: https://allride-voucher-consumer.mintlify.app/consumer/catalog Which providers and voucher sets you can redeem, how availability works, and how Provider and Voucher Set Toggles control what your channels show. Your catalog is the list of Partner Providers and voucher sets that AllRide makes available to you. You read it with the [catalog operations](/api-reference/consumer-catalog-voucher-sets-list), decide with Catalog Toggles which entries your apps and landing pages show, and keep your own settings and references next to each entry. ## What your catalog contains A voucher set appears in your catalog when all of these hold: * the set has an approved live revision (drafts and pending change requests are never visible to you); * its effective status is `scheduled`, `active` or `deactivation-scheduled`; * its provider is active; * AllRide has granted you access: the provider's Provider Access allows you, and the set's Consumer Access allows you too. Access is managed by AllRide, and a provider is visible to no consumer until AllRide grants access. A provider is listed when at least one of its voucher sets is visible to you; a provider you have no access to is absent, and asking for it by id answers 404 [`provider-not-found`](/errors/catalog#provider-not-found). A set that is not visible to you answers 404 [`voucher-set-not-found`](/errors/catalog#voucher-set-not-found). The catalog has three resources: | Resource | Operations | Contents | | - | - | - | | `consumer-catalog-provider` | [List](/api-reference/consumer-catalog-providers-list), [get](/api-reference/consumer-catalog-providers-get), [update](/api-reference/consumer-catalog-providers-update) | The provider's live profile (name, tagline, terms, app links), its Listing Branding, counts of visible sets per availability, the Provider Toggle and your provider settings | | `consumer-catalog-entry` | [List](/api-reference/consumer-catalog-voucher-sets-list), [get](/api-reference/consumer-catalog-voucher-sets-get), [update](/api-reference/consumer-catalog-voucher-sets-update) | One voucher set: its approved content (`live`), voucher kind (`code` or `link`), availability and schedule, `stockStatus`, both toggles, `priceAcceptance` and your set settings | | `category` | [List](/api-reference/consumer-catalog-categories-list), [get](/api-reference/consumer-catalog-categories-get) | The active categories AllRide maintains, with localised names and an icon | Reading the catalog needs the permission `view-consumer-catalog`; changing toggles and settings needs `manage-consumer-catalog`. Both can be granted to API keys. ```bash theme={null} curl "$VOUCHER_API_BASE_URL/consumer/v1/catalog/voucher-sets?filter%5BproviderToggle%5D=true&filter%5BvoucherSetToggle%5D=true&filter%5Bavailability%5D=active&perPage=50" \ -H "Authorization: Bearer $VOUCHER_API_KEY" ``` Each entry carries `priceAcceptance` with a `status` and a `nextChangeAt`: | `priceAcceptance.status` | Meaning | Redeemable | | - | - | - | | `accepted` | You accepted the Price Terms in force now | Yes | | `change-pending` | You accepted the current terms; a price change starts at `nextChangeAt` and is not yet accepted | Yes, until the new terms start | | `acceptance-required` | You have not accepted the Price Terms in force now | No: redeeming answers 409 [`price-acceptance-required`](/errors/catalog#price-acceptance-required) | | `not-priced` | The set has no price yet | No | | `exempt` | Your partner is not billed for this set | Yes | A missing Price Acceptance never hides an entry from the catalog. Accept prices on the Prices page of the consumer dashboard, as described in [Prices and price acceptance](/consumer/prices). The entry never shows the terms themselves; read them through the pricing operations. ## Availability `availability` tells you where a set is in its lifecycle as you see it: | `availability` | Meaning | | - | - | | `upcoming` | Approved and scheduled; `activeFrom` says when it becomes redeemable | | `active` | Redeemable now (subject to your toggles, stock and Price Acceptance) | | `deactivation-scheduled` | Redeemable now and ends at `deactivateAt` | Availability is computed from the clock at the moment of your request, so a set whose `activeFrom` has passed is `active` immediately. A provider's `availability` is `active` when any of its visible sets is active, else `deactivation-scheduled` when any is, else `upcoming`. Use `nextActiveFrom` and `earliestDeactivateAt` on the provider to show "coming soon" and "leaving soon" labels. Stock is shown only as `stockStatus` (`available`, `low` or `depleted`), never as numbers. `available` does not promise more than one voucher; see [batch redemptions](/consumer/redeeming#batch-redemptions). ## Provider and voucher set toggles Catalog Toggles let you choose what your channels offer without changing what AllRide makes visible: * the **Provider Toggle** (`providerToggle`, set through `setting.enabled` on the provider) is **off** by default; * the **Voucher Set Toggle** (`voucherSetToggle`, set through `setting.enabled` on the entry) defaults to the provider's `setting.autoEnableNewVoucherSets`, which is **on** by default. A voucher set is redeemable through the API and shown on landing pages only when both toggles are on. Redeeming a set whose provider or set toggle is off answers 409 [`voucher-set-unavailable`](/errors/catalog#voucher-set-unavailable). Toggles never hide entries from the catalog operations: the list always returns every visible entry with its toggle values, so your management screens can show what is switched off. Apps and landing pages filter with `filter[providerToggle]=true&filter[voucherSetToggle]=true`. ```bash theme={null} curl -X PATCH "$VOUCHER_API_BASE_URL/consumer/v1/catalog/providers/1030" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"setting": {"enabled": true, "autoEnableNewVoucherSets": false}}' ``` `autoEnableNewVoucherSets` is not retroactive. When you change it, every set of that provider that is already visible keeps its current Voucher Set Toggle; only sets that become visible afterwards start with the new value. You can set toggles on `upcoming` sets so they are on when the set activates. ## Your own settings and references Both the provider and the entry carry a `setting` object that only you see. Update it with a JSON Merge Patch: properties you leave out stay unchanged and `null` clears a value. | Field | Provider | Voucher set | Notes | | - | - | - | - | | `enabled` | Yes | Yes | The toggle | | `autoEnableNewVoucherSets` | Yes | No | Initial toggle of sets that become visible later | | `position` | Yes | Yes | Your sort order (0 or more); the entry's effective `position` falls back to the provider's | | `featured` | Yes | Yes | Highlight flag; an entry is `featured` when the set or its provider is | | `categoryOverrideId` | Yes | Yes | Show the entry in another active category (`effectiveCategoryId`) | | `entityLimit` | No | Yes | `{max, period}` with `period` `lifetime` or `calendar-month`: how often one Redemption Entity may redeem this set | | `settings` | Yes | Yes | Your own JSON object, replaced as a whole | | `externalRef` | Yes | Yes | Your own reference (1-64 characters), unique among your provider settings and among your set settings | ```bash theme={null} curl -X PATCH "$VOUCHER_API_BASE_URL/consumer/v1/catalog/voucher-sets/2030" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"setting": {"enabled": true, "entityLimit": {"max": 1, "period": "calendar-month"}, "externalRef": "ride-credit-5"}}' ``` * Look entries up by your reference with `filter[setting.externalRef]=ride-credit-5`. A reference that is already used answers 409 [`external-ref-taken`](/errors/catalog#external-ref-taken). * A PATCH writes only the properties you send, so two systems that change different properties of the same setting both succeed. * Calendar months of `entityLimit` follow your partner time zone. Changing the `period` starts counting from zero under the new period; lowering `max` below the current count blocks further redemptions in that period. ## Keeping a local copy Apps that show the catalog to end customers should not call the API on every screen view. A pattern that works: 1. Load the catalog with the toggle filters and cache it for at most 60 seconds. 2. Subscribe a [webhook endpoint](/webhooks/overview) to the `catalog-*` events and refresh the affected entry when one arrives: [`catalog-voucher-set-available`](/webhooks/events/catalog-voucher-set-available), [`updated`](/webhooks/events/catalog-voucher-set-updated), [`activation-scheduled`](/webhooks/events/catalog-voucher-set-activation-scheduled), [`activated`](/webhooks/events/catalog-voucher-set-activated), [`deactivation-scheduled`](/webhooks/events/catalog-voucher-set-deactivation-scheduled), [`deactivated`](/webhooks/events/catalog-voucher-set-deactivated), [`unavailable`](/webhooks/events/catalog-voucher-set-unavailable), [`stock-low`](/webhooks/events/catalog-voucher-set-stock-low) and [`depleted`](/webhooks/events/catalog-voucher-set-depleted). 3. Treat a 409 from a redemption as the final word: the cache can be up to a minute old, and redeeming always checks the current state. `catalog-*` webhooks are sent for every set visible to you, whatever your toggles say, so you also learn about new sets you might want to switch on. `catalog-voucher-set-unavailable` names the reason, for example `access-revoked` or `voucher-set-archived`. ## Related * [Redeeming vouchers](/consumer/redeeming) * [Prices and price acceptance](/consumer/prices) * [Pagination, filtering and sorting](/guides/pagination) * [Event catalog](/webhooks/events) # Email OTP links Source: https://allride-voucher-consumer.mintlify.app/consumer/email-otp-links Require visitors of a Redemption Link to verify their email address with a one-time code sent through your own SMTP server. An email OTP link (`requireEmailOtp: true`, mode `email-otp`) asks each visitor for an email address and sends a 6-digit one-time code to it. After verifying the code, the visitor can redeem, and the platform finds or creates a [Redemption Entity](/consumer/redemption-entities) for that address, so per-entity limits apply per person. This is the Email OTP Gate. ## Requirements You can create an `email-otp` link only when all three hold: 1. the Email OTP Gate is turned on (`emailOtpLinksEnabled`); 2. your SMTP configuration is `verified`; 3. you have an `active` email template for the purpose `redemption-link-otp`. Otherwise creating the link answers 422 [`email-otp-not-configured`](/errors/catalog#email-otp-not-configured), and `more.missing` lists what is missing: `email-otp-disabled`, `smtp-not-verified` or `no-active-template`. Check the state before you create links with [`GET /consumer/v1/email-otp-settings/readiness`](/api-reference/consumer-email-otp-readiness-get). It needs only `manage-consumer-redemption-links`, so link-creating integrations can call it: ```bash theme={null} curl "$VOUCHER_API_BASE_URL/consumer/v1/email-otp-settings/readiness" \ -H "Authorization: Bearer $VOUCHER_API_KEY" ``` ```json theme={null} { "data": { "resource": "email-otp-readiness", "ready": false, "missing": ["smtp-not-verified"], "_links": { "self": { "href": "https://api.voucher.allride.io/consumer/v1/email-otp-settings/readiness", "type": "application/json" } } } } ``` Every other operation on this page needs `manage-consumer-email`. It can be granted to API keys; dashboard users need a recent sign-in for the SMTP configuration and for switching the gate. ## Configure SMTP OTP emails are sent through **your** SMTP server, from your address. Save the configuration with [`PUT /partner/v1/smtp-configuration`](/api-reference/partner-smtp-configuration-replace): ```bash theme={null} curl -X PUT "$VOUCHER_API_BASE_URL/partner/v1/smtp-configuration" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "host": "smtp.example-transit.example.com", "port": 587, "security": "starttls", "username": "voucher-mailer", "password": "use-your-smtp-password", "fromEmail": "no-reply@example.com", "fromName": "Example Transit vouchers", "replyTo": null }' ``` * Ports: **465** with `security: implicit-tls`, or **587** or **2525** with `starttls`. Port 25 is not allowed, and a port that does not match `security` fails with the constraint `security-port-mismatch`. * The host must resolve to a public address. Private, loopback, link-local and cloud metadata addresses, and hosts that do not resolve, answer 422 [`smtp-host-not-allowed`](/errors/catalog#smtp-host-not-allowed). * The `password` is write-only: it is encrypted and never returned (`passwordSet` tells you one is stored). On later PUTs you can omit it to keep the stored one. * The first PUT answers 201; later PUTs replace the configuration and answer 200. * Emails leave from AllRide's egress IP addresses. If your SMTP server filters senders, allow them; see [Network and IP addresses](/guides/network). Every save sets the status to `unverified`. Then verify with [`POST /partner/v1/smtp-configuration:verify`](/api-reference/partner-smtp-configuration-verify): the platform sends a test email in the background, to the requesting dashboard user or, for an API key, to your partner's technical contact (422 [`smtp-test-recipient-missing`](/errors/catalog#smtp-test-recipient-missing) when none is set). Pass an `emailTemplateId` to receive that template rendered with sample data. Poll [`GET /partner/v1/smtp-configuration`](/api-reference/partner-smtp-configuration-get) until `verificationCompletedAt` is at or after `verificationRequestedAt`, then read the result: `status: verified`, or `lastError` with the server's reply. Only one verification runs at a time (409 [`smtp-verification-in-progress`](/errors/catalog#smtp-verification-in-progress)). Saving new credentials does not interrupt live `email-otp` links: they keep sending through the new configuration while it is `unverified`, but you cannot create new `email-otp` links until it is verified again. ## Customise the email template A default template is generated from your Workspace Branding when your partner is set up, so you can start with it. To use your own HTML: 1. Create a draft with [`POST /partner/v1/email-templates`](/api-reference/partner-email-templates-create): upload `subject`, `htmlBody` (one HTML document per locale, at most 100 KB each, including your default locale) and optionally `textBody`, or send `generateFromBranding: true` to generate a fresh draft from your current branding. 2. Check `validation` in the response and render a preview with [`GET …/{emailTemplateId}/preview?locale=de`](/api-reference/partner-email-templates-preview). The preview uses sample data (code `123456`, 10 minutes). 3. Edit drafts with [`PATCH`](/api-reference/partner-email-templates-update) until `valid` is `true`. 4. Activate with [`POST …/{emailTemplateId}:activate`](/api-reference/partner-email-templates-activate). The previous active template is archived in the same step, and the next OTP email uses the new one. Templates use Handlebars variables: | Variable | Required | Value | | - | - | - | | `{{otpCode}}` | Yes | The 6-digit code | | `{{expiresInMinutes}}` | Yes | Minutes until the code expires (10) | | `{{partnerName}}` | No | Your partner name | | `{{logoUrl}}` | No | Your email logo (or logo) URL | | `{{primaryColor}}` | No | Your primary colour | | `{{supportEmail}}` | No | The support email of your landing page | | `{{linkName}}` | No | The Redemption Link's `name` | | `{{locale}}` | No | `de` or `en` | Only the helpers `if`, `unless` and `each` are allowed. Triple braces, partials, decorators and dotted paths are refused with 422 [`template-invalid`](/errors/catalog#template-invalid). Scripts and remote images from other hosts than AllRide's asset host are reported in `validation.htmlFindings` and block activation. A template is activated only when no required variable is missing, no unknown variable is used and it renders. [`GET /partner/v1/email-template-contracts/redemption-link-otp`](/api-reference/partner-email-template-contracts-get) returns this contract for editors. Active templates cannot be edited (409 [`email-template-not-editable`](/errors/catalog#email-template-not-editable)): create a new draft and activate it. The active template cannot be archived while the gate is on (409 [`email-template-in-use`](/errors/catalog#email-template-in-use)). ## Turn on email OTP links When SMTP is verified and a template is active, turn the gate on with [`PATCH /consumer/v1/email-otp-settings`](/api-reference/consumer-email-otp-settings-update): ```bash theme={null} curl -X PATCH "$VOUCHER_API_BASE_URL/consumer/v1/email-otp-settings" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "emailOtpLinksEnabled": true }' ``` Then create links with `requireEmailOtp: true` and no entity (see [Redemption links](/consumer/redemption-links#link-modes)). Set `maxRedemptions` (per verified person) and `maxTotalRedemptions` (for the whole link). Turning the gate off is always allowed. Existing `email-otp` links stay `active`, but visitors can no longer request codes; visitors who already verified can redeem until their session ends. `activeEmailOtpLinkCount` in [`GET /consumer/v1/email-otp-settings`](/api-reference/consumer-email-otp-settings-get) tells you how many links are affected. ## Session length After a visitor verifies their email, they stay signed in on the landing page for **30 minutes** by default. Set your own length between 15 and 1440 minutes (24 hours) with `sessionDurationMinutes`, in the dashboard under **Email verification** or with the API: ```bash theme={null} curl -X PATCH "$VOUCHER_API_BASE_URL/consumer/v1/email-otp-settings" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sessionDurationMinutes": 240 }' ``` The new length applies to visitors who verify after the change; visitors who already verified keep their end time. When the session ends, the page tells the visitor how long sessions last and asks them to verify again. Links without email verification (`anonymous` and `entity-bound`) do not use this setting: their visitors stay on the page as long as the link is valid. Values outside 15 to 1440 answer 400 [`data-failed-validation`](/errors/catalog#data-failed-validation). ## What the visitor does On the landing page the visitor enters an email address, receives the code, and enters it. Verification finds or creates the entity for that address (the [`redemption-entity-created`](/webhooks/events/redemption-entity-created) webhook is sent for new ones, with the email); a second link for the same address reuses the same entity. A blocked entity cannot redeem. ## Limits | Limit | Value | Visitor sees | | - | - | - | | Code validity | 10 minutes | Request a new code | | Wrong codes per code | 5; the 5th locks the code | Request a new code | | Code requests per email address and link | 5 per hour | "Try again later" | | Code requests per IP address | 30 per hour | "Try again later" | | Resend cooldown | 1 request per 60 seconds per visitor session | "Try again later" | | Code requests per email address across all your links | 10 per 24 hours | No visible error; no further email is sent | | Failed verifications per email address across all your links | 20 per 24 hours | No visible error; codes stop verifying until the window passes | | Visitor session after verification | Your `sessionDurationMinutes` (default 30, 15 to 1440) | Verify again | The last two limits are silent on purpose, so nobody can use your SMTP server to flood one address or learn that an address is being targeted. When you turned on bot protection for your landing page, every code request also passes a Cloudflare Turnstile check. ## Related * [Redemption links](/consumer/redemption-links) * [Landing pages and custom domains](/consumer/landing-pages) * [Network and IP addresses](/guides/network) * [Get email OTP readiness](/api-reference/consumer-email-otp-readiness-get) # Landing pages and custom domains Source: https://allride-voucher-consumer.mintlify.app/consumer/landing-pages Brand the Redemption Link landing page with your Workspace Branding, set your legal links, choose a Landing Look, turn on bot protection and serve it on your own domain. When an end customer opens a [Redemption Link](/consumer/redemption-links), AllRide renders a landing page for you: your branding, the voucher sets the link allows, a search over providers and, after redeeming, the issued code or link. You configure the page once for your whole partner through **Workspace Branding**; every link uses it. The page is available in German and English. ## Branding The landing page takes its look from your Workspace Branding, the same branding that styles your dashboards. You edit it in the consumer dashboard or with the API: * [Get workspace branding](/api-reference/partner-workspace-branding-get) is readable by every member of your partner and by every API key, without a permission. * [Replace workspace branding](/api-reference/partner-workspace-branding-replace) (`PUT /partner/v1/branding/workspace`) needs `manage-workspace-branding`, which you can delegate to an API key. It replaces the whole branding document, so send every field you want to keep. Workspace Branding needs no approval by AllRide. Changes reach open landing pages within 60 seconds. | Part | Fields | Notes | | - | - | - | | Colours | `primaryColor`, `secondaryColor`, `backgroundColor`, `surfaceColor`, `textColor`, `accentColor`, `buttonColor`, `buttonTextColor`, `buttonBorderColor` | `#rrggbb`. Colour pairs must reach WCAG AA contrast, otherwise the save fails with [`insufficient-contrast`](/errors/catalog#insufficient-contrast) | | Images | `logoAssetId`, `logoDarkAssetId`, `iconAssetId`, `heroImageAssetId`, `faviconAssetId`, `emailLogoAssetId` | Upload images first as brand assets ([Create a brand asset](/api-reference/partner-brand-assets-create), then [Complete an upload](/api-reference/partner-brand-assets-complete)) | | Typography and shape | `fontFamily`, `cornerRadius` | | | Landing page | `landing` | The landing block below; only partners with the consumer role can send it | ### The `landing` block | Field | Type | What it does | | - | - | - | | `title` | Localised text, up to 80 characters | Main heading. Without it the page shows "Your vouchers from" followed by your partner name | | `intro` | Localised restricted Markdown, up to 1,000 characters | Introduction below the heading | | `termsUrl` | https URL, optional | Your terms for the voucher programme | | `privacyUrl` | https URL | Your privacy policy. **Required before you can create Redemption Links** | | `imprintUrl` | https URL | Your imprint (Impressum). **Required before you can create Redemption Links** | | `supportEmail`, `supportUrl` | Email, https URL, optional | Shown as help links on the page and in every error or unavailable state | | `footerText` | Localised text, up to 300 characters | Plain text in the footer | | `showProviderSearch` | Boolean, default `true` | Shows the search field when the link offers at least two providers; a link that offers one provider never shows it | | `look` | `vibrant`, `soft`, `bold` or `clean` | The Landing Look, default `vibrant` | | `botProtection` | `off` or `turnstile` | Bot check on the page, default `off` | Localised fields are objects with a `de` and an `en` text. Include at least your partner's default locale; add both so that German and English visitors see your own words. ### Landing Look The Landing Look decides how colourful the page is. AllRide derives the page colours from your branding for the Look you pick; colours you set yourself are used unchanged. Layout, content and order are the same in every Look. | Look | Character | | - | - | | `vibrant` (default) | Gradients and decorative shapes in your brand colours | | `soft` | Pastel tints of your colours | | `bold` | Saturated, solid colour blocks | | `clean` | Restrained and lightly tinted, for conservative brands | The Look applies only to landing pages. It does not change your dashboards or the generated OTP email. ### Bot protection (Cloudflare Turnstile) Bot protection is off by default. Turn it on with `landing.botProtection: "turnstile"` if your links are shared widely, for example anonymous links in a newsletter. * **`off`**: no security check runs on the page. Link limits, entity limits and the [Email OTP Gate](/consumer/email-otp-links) still apply. * **`turnstile`**: the page runs Cloudflare Turnstile before an end customer requests an OTP email and before they redeem, and again when they enter a wrong code more than once. Most visitors never see a challenge; Turnstile asks for an interaction only when it is unsure. The setting applies to every Redemption Link of your partner and takes effect on the next page load. With Turnstile on, end customers who block `challenges.cloudflare.com` cannot redeem, and the page tells them so. ### Example ```bash theme={null} curl -X PUT "$VOUCHER_API_BASE_URL/partner/v1/branding/workspace" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -d @branding.json ``` ```json theme={null} { "primaryColor": "#0b6e4f", "secondaryColor": null, "backgroundColor": "#ffffff", "surfaceColor": "#f5f5f5", "textColor": "#1a1a1a", "accentColor": null, "buttonColor": "#0b6e4f", "buttonTextColor": "#ffffff", "buttonBorderColor": null, "logoAssetId": 3095, "logoDarkAssetId": null, "iconAssetId": null, "heroImageAssetId": null, "faviconAssetId": null, "emailLogoAssetId": null, "fontFamily": "source-sans-3", "cornerRadius": "md", "extensions": null, "landing": { "title": { "de": "Ihre Mobilitätsgutscheine", "en": "Your mobility vouchers" }, "intro": { "de": "Wählen Sie einen Anbieter.", "en": "Choose a provider." }, "termsUrl": "https://www.example-transit.example.com/vouchers/terms", "privacyUrl": "https://www.example-transit.example.com/privacy", "imprintUrl": "https://www.example-transit.example.com/imprint", "supportEmail": "vouchers@example.com", "supportUrl": null, "footerText": null, "showProviderSearch": true, "look": "vibrant", "botProtection": "off" } } ``` ### Content rules Every text, Markdown and URL value is checked when you save. A value that breaks a content rule (hidden characters, HTML or scripts, encoded data, look-alike brand names, unsafe links, requests for passwords or payment details) is refused with [`content-blocked`](/errors/catalog#content-blocked); `more.validationErrors` names each field and the reason. Markdown supports paragraphs, bold, italic, lists, headings level 3 and 4 and https links. ## Terms and legal links The page footer shows your legal links from the `landing` block: | Footer link | Field | Required | | - | - | - | | Terms and conditions | `landing.termsUrl` | No | | Privacy | `landing.privacyUrl` | Yes | | Imprint | `landing.imprintUrl` | Yes | | Help and email | `landing.supportUrl`, `landing.supportEmail` | No | Two rules keep the legal links in place: * You cannot create or reactivate a Redemption Link while `privacyUrl` or `imprintUrl` is missing: the request fails with 422 [`landing-legal-urls-missing`](/errors/catalog#landing-legal-urls-missing). * You cannot remove `privacyUrl` or `imprintUrl` (or send `landing: null`) while any of your links is not revoked, including exhausted and expired links, because their pages are still shown. The save fails with 409 [`landing-legal-urls-required`](/errors/catalog#landing-legal-urls-required) and `more.nonRevokedLinkCount`. Revoke the links first. Provider and voucher terms come from the provider's content. The page first lists every provider the link offers; an end customer expands a provider to see its voucher sets and opens a voucher set's details in a dialog, which shows the voucher set's own voucher terms. App Store and Google Play links and the provider's terms, privacy and website pages belong to the provider and appear at the top of every voucher set's details dialog and next to the issued voucher (also for vouchers issued before the provider changed them), not in the provider list. Every voucher set's button reads "Redeem" ("Einlösen"). A link that offers only one provider shows that provider and its voucher sets straight away, without the provider list and without the search field. Before redeeming, the page states that the end customer accepts the voucher terms of the provider and, when you set `termsUrl`, your terms as well. All partner links open in a new tab. ## Custom domains for landing pages By default, links point to `https://redeem.voucher.allride.io/r/`. To use your own host name, for example `vouchers.example-transit.example.com`, add a Custom Domain: 1. Add the domain in the developer dashboard ([Custom domains](https://consumer-developers.voucher.allride.io/go/custom-domains)) or with [Add a custom domain](/api-reference/partner-custom-domains-create) (`manage-domains`, delegable to API keys). Only subdomains work, not the apex of your domain. 2. Create the DNS records the domain shows: a `CNAME` to `customers.voucher.allride.io` plus the ownership and certificate validation `TXT` records. 3. Wait until the domain is `active`. [Re-check a custom domain](/api-reference/partner-custom-domains-refresh) asks for an immediate check. A link uses its own `customDomainId` when that domain is `active`, otherwise your default landing domain (`settings.defaultLandingDomainId` of your partner, set in the dashboard) when it is `active`, otherwise `redeem.voucher.allride.io`. When you delete a domain, links that used it fall back to the next host in that order and the old host name stops serving pages. Custom Domains serve only landing pages; your dashboards always run on AllRide host names. The full setup, statuses and troubleshooting are in [Custom domains](/workspace/custom-domains). ## Embedding Landing pages cannot be embedded in an iframe: they send `frame-ancestors 'none'` and `X-Frame-Options: DENY`. To show a link inside your app, open it top-level, in an in-app browser or in a WebView; all of these work. The page language follows the `lang` query parameter (`?lang=en` or `?lang=de`), then the visitor's browser language, then your partner's default locale. Append `?lang=en` to a link URL if you know your customer's language. End customers can switch the language on the page. ## Related * [Redemption links](/consumer/redemption-links) * [Email OTP links](/consumer/email-otp-links) * [Custom domains](/workspace/custom-domains) * [Get workspace branding](/api-reference/partner-workspace-branding-get) * [Replace workspace branding](/api-reference/partner-workspace-branding-replace) # Prices and price acceptance Source: https://allride-voucher-consumer.mintlify.app/consumer/prices Read your Price Terms per voucher set, accept them in the consumer dashboard before you redeem, and handle price changes and the price gate. AllRide decides which voucher sets are available to you and at what price per redemption. You see your own price for every voucher set in your catalog and must **accept** it before you can redeem from that set. No consumer is ever billed at a price it has not accepted. ## Price terms For every voucher set visible to you, your **Price Terms** state what one redemption costs you: | Field | Meaning | | - | - | | `pricingModel` | `per-unit` (one price per redemption) or `graduated` (tiers, each with its own price) | | `unitPrice` | For `per-unit`: a `UnitPrice` `{value, scale: 4, label}` with `value` in ten-thousandths of a euro (`1000` = €0.10, `1250` = €0.1250). A price of zero is possible | | `tiers` | For `graduated`: the tiers in order, each with `upTo` (the last redemption number it covers, `null` on the last tier) and `unitPrice` | Prices change over time, so the terms come in **Price Terms Periods**: a `current` period (the one in force now, `null` when the set has no price yet) and up to 5 `upcoming` periods, each with `startsAt`, `endsAt` (`null` when open-ended), `terms`, a `termsDigest` (a SHA-256 fingerprint of the terms) and your `acceptance`. | Operation | Endpoint | | - | - | | [List Price Terms](/api-reference/consumer-pricing-voucher-sets-list) | `GET /consumer/v1/pricing/voucher-sets` (filter by `providerPartnerId`, `voucherSetId`, `status`, `hasUpcomingChange`) | | [Get Price Terms of a voucher set](/api-reference/consumer-pricing-voucher-sets-get) | `GET /consumer/v1/pricing/voucher-sets/{voucherSetId}` | | [Get the pricing summary](/api-reference/consumer-pricing-summary-get) | `GET /consumer/v1/pricing/summary`: how many sets need acceptance, have a pending change or have no price, and the next change | | [List Price Acceptances](/api-reference/consumer-pricing-acceptances-list) | `GET /consumer/v1/pricing/acceptances`: everything you have accepted | ```bash theme={null} curl "$VOUCHER_API_BASE_URL/consumer/v1/pricing/voucher-sets/2030" \ -H "Authorization: Bearer $VOUCHER_API_KEY" ``` ```json theme={null} { "data": { "id": 2030, "resource": "voucher-set-price-terms", "voucherSetId": 2030, "providerPartnerId": 1030, "provider": { "id": 1030, "resource": "partner", "name": "Acme Scooters", "_links": { "self": { "href": "https://api.voucher.allride.io/consumer/v1/catalog/providers/1030", "type": "application/json" } } }, "voucherSetName": { "de": "Acme Starter Pass", "en": "Acme Starter Pass" }, "status": "change-pending", "nextChangeAt": "2026-11-30T23:00:00Z", "current": { "startsAt": "2025-12-31T23:00:00Z", "endsAt": "2026-11-30T23:00:00Z", "terms": { "pricingModel": "per-unit", "unitPrice": { "value": 800, "scale": 4, "label": "€0.08" }, "tiers": null }, "termsDigest": "4f0c2a9e7b13d5c8e1f6a0b2c4d6e8f0a1b3c5d7e9f1a3b5c7d9e1f3a5b7c9d1", "acceptBy": "2025-12-31T23:00:00Z", "acceptance": { "status": "accepted", "priceAcceptanceId": 61017, "acceptedAt": "2026-01-03T09:12:00Z", "acceptedBy": { "type": "partner-user", "id": 4069, "label": "finance@example.com" } } }, "upcoming": [ { "startsAt": "2026-11-30T23:00:00Z", "endsAt": null, "terms": { "pricingModel": "per-unit", "unitPrice": { "value": 1000, "scale": 4, "label": "€0.10" }, "tiers": null }, "termsDigest": "9a17e3c5b7d9f1a3c5e7a9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1", "acceptBy": "2026-11-30T23:00:00Z", "acceptance": { "status": "pending" } } ], "_links": { "self": { "href": "https://api.voucher.allride.io/consumer/v1/pricing/voucher-sets/2030", "type": "application/json" }, "voucherSet": { "href": "https://api.voucher.allride.io/consumer/v1/catalog/voucher-sets/2030", "type": "application/json" } } } } ``` Instants are UTC; `2026-11-30T23:00:00Z` is midnight on 1 December in Berlin. You see only the price that applies to you, never how AllRide derived it, and never the prices of other consumers. Providers never see prices, and end customers never see prices on landing pages. **Availability.** Which voucher sets you see at all is also decided by AllRide, per provider and per voucher set. A set you have no access to is absent from your catalog and from the pricing endpoints (404 [`voucher-set-not-found`](/errors/catalog#voucher-set-not-found)). See [Catalog and toggles](/consumer/catalog). ## Accepting prices Accepting a price is a commercial commitment, so a person in your team makes it: * Accept on the **Prices** page of the consumer dashboard. It lists the sets that need acceptance and the terms you accept. * The member needs the permission `accept-consumer-pricing` and a sign-in within the last 15 minutes. The default groups `owners`, `administrators` and `billing` have it. * **API keys cannot accept prices**: `accept-consumer-pricing` cannot be delegated, and an API key calling the accept operation gets 403 [`missing-permission`](/errors/catalog#missing-permission). AllRide cannot accept on your behalf either. The dashboard calls [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create) (`POST /consumer/v1/pricing/acceptances`) with the periods it showed, identified by voucher set, `periodStartsAt` and `termsDigest`. Up to 200 periods are accepted in one all-or-nothing request. You can accept the current period or any upcoming one in advance. Accepting the same terms again changes nothing. Every acceptance is immutable and recorded in your [audit log](/workspace/audit-log) with the exact terms, who accepted and when. If the terms changed while you were deciding, the request fails with 409 [`price-terms-changed`](/errors/catalog#price-terms-changed) and nothing is recorded; reload the Prices page and accept again. A suspended partner cannot accept prices. ## Price changes When AllRide changes a price you have accepted, the new price starts **at least 14 days later** (the platform notice period). Only in exceptional cases, with a recorded reason, can AllRide schedule a change at shorter notice, never earlier than the next day. You hear about a change through: * an email to your members with `accept-consumer-pricing`, with reminders 7 days and 1 day before the start; * the webhook [`price-terms-change-scheduled`](/webhooks/events/price-terms-change-scheduled); * a banner in the consumer dashboard and the counts of the [pricing summary](/api-reference/consumer-pricing-summary-get). Until the new price starts you keep redeeming, and are billed, at the price you accepted. **From the start date the voucher set pauses for you until you accept the new price**; you then receive [`price-terms-acceptance-required`](/webhooks/events/price-terms-acceptance-required). Accept before the start date (`acceptBy`) to avoid a pause. The `status` of a voucher set (also shown on [catalog entries](/consumer/catalog) as `priceAcceptance.status`) tells you where you stand: | `status` | Redeemable | Meaning | | - | - | - | | `accepted` | Yes | You accepted the terms in force, and no unaccepted change is coming | | `change-pending` | Yes, until `nextChangeAt` | A new price starts at `nextChangeAt` and you have not accepted it yet | | `acceptance-required` | No | You have not accepted the terms in force now | | `not-priced` | No | AllRide has not set a price for you yet | | `exempt` | Yes | No acceptance is needed for your partner | ## Redeeming and the price gate A voucher set is redeemable for you only when you have accepted the Price Terms in force at the moment of the redemption. Otherwise: * [Create a redemption](/api-reference/consumer-redemptions-create) and [batch redemptions](/api-reference/consumer-redemptions-batch) answer 409 [`price-acceptance-required`](/errors/catalog#price-acceptance-required). `more` names the `voucherSetId` and the `priceAcceptanceStatus` (`acceptance-required` or `not-priced`). Accept the price in the dashboard, then retry with a **new** `Idempotency-Key` (the 409 is stored for the old key). * When you redeem by `providerId`, sets you have not accepted are skipped; the 409 occurs only when nothing else blocks the redemption. * On [landing pages](/consumer/landing-pages) a set without an accepted price is absent, as if it were switched off. End customers never see prices or an acceptance message. Watch `priceAcceptance.status` on your catalog entries, or the [pricing summary](/api-reference/consumer-pricing-summary-get), to find sets that need attention before your customers notice. ## Pricing webhooks These consumer-only webhook types require `view-consumer-pricing` on the member or API key that subscribes the endpoint. | Type | Sent when | `payload` | | - | - | - | | [`price-terms-change-scheduled`](/webhooks/events/price-terms-change-scheduled) | AllRide schedules a change to terms you have accepted | The price change (`resource: price-change`, `id`), `startsAt` and the affected `voucherSets` with their Price Terms | | [`price-terms-acceptance-required`](/webhooks/events/price-terms-acceptance-required) | A change started and some of your sets are paused until you accept | The price change (`resource: price-change`, `id`), `startsAt`, `voucherSetIds` | | [`price-terms-accepted`](/webhooks/events/price-terms-accepted) | A member of your team accepted Price Terms | The Price Acceptance | ## Permissions | Permission | Grants | API keys | | - | - | - | | `view-consumer-pricing` | Read Price Terms, the pricing summary and your Price Acceptances; subscribe to pricing webhooks | Yes (delegable) | | `accept-consumer-pricing` | Accept Price Terms (dashboard users with a recent sign-in) | No | The default groups `owners`, `administrators` and `billing` hold both. `developers`, `viewers` and `redemption-operators` hold neither, because prices are finance data. ## Related * [Billing](/consumer/billing) * [Catalog and toggles](/consumer/catalog) * [Redeeming vouchers](/consumer/redeeming) * [Members and groups](/workspace/members-and-groups) # Redeeming vouchers Source: https://allride-voucher-consumer.mintlify.app/consumer/redeeming 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`](/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](/consumer/catalog), its availability is `active` or `deactivation-scheduled`, both Catalog Toggles are on, you have accepted its current [Price Terms](/consumer/prices) 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`](/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`](/errors/catalog#provider-depleted); when every candidate was skipped for its entity limit, 409 [`entity-limit-reached`](/errors/catalog#entity-limit-reached). ## Attaching a redemption entity A [Redemption Entity](/consumer/redemption-entities) 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`](/errors/catalog#entity-limit-reached). A blocked entity answers 409 [`redemption-entity-blocked`](/errors/catalog#redemption-entity-blocked), an erased one 409 [`redemption-entity-erased`](/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`](/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`](/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}`](/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[]`. * Every redemption of the batch stores the batch's `Idempotency-Key`, so `GET /consumer/v1/redemptions?filter[idempotencyKey]=` 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}`](/api-reference/consumer-redemptions-get) or [list redemptions](/api-reference/consumer-redemptions-list), for example filtered by `redemptionEntity.refId` or `redeemedAt`. Files downloaded from the dashboard show them masked. You also receive the webhook [`redemption-created`](/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`](/errors/catalog#voucher-set-not-found) | The set is unknown or not visible to you | Refresh your catalog | | 404 | [`provider-not-found`](/errors/catalog#provider-not-found) | `providerId` has no set visible to you | Refresh your catalog | | 404 | [`redemption-entity-not-found`](/errors/catalog#redemption-entity-not-found) | `redemptionEntityId` is unknown | Check the id | | 409 | [`voucher-set-unavailable`](/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`](/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`](/errors/catalog#voucher-set-depleted) | No claimable voucher is left | Offer another set, or redeem by `providerId` | | 409 | [`provider-depleted`](/errors/catalog#provider-depleted) | Every redeemable set of the provider is depleted | Offer another provider | | 409 | [`entity-limit-reached`](/errors/catalog#entity-limit-reached) | The entity reached the set's `entityLimit` | Tell the customer when the next period starts | | 409 | [`redemption-entity-blocked`](/errors/catalog#redemption-entity-blocked) | The entity is blocked | Unblock it if appropriate | | 409 | [`redemption-entity-erased`](/errors/catalog#redemption-entity-erased) | The entity was erased | Create a new entity | | 400 | [`data-failed-validation`](/errors/catalog#data-failed-validation) | The body is invalid; each problem is in `more.validationErrors` | Fix the request | | 422 | [`batch-too-large`](/errors/catalog#batch-too-large) | A batch asks for more than 10 vouchers | Split the batch | | 500 | [`link-render-failed`](/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`](/errors/catalog#db-unavailable) | Temporary overload | Retry with the same `Idempotency-Key` after `Retry-After` | A suspended partner gets 403 [`partner-suspended`](/errors/catalog#partner-suspended). See [Errors](/errors) 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](/guides/idempotency). * **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]=`: 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]=&sort=redeemedAt` and de-duplicate by `id`. ## Related * [Catalog and toggles](/consumer/catalog) * [Redemption entities](/consumer/redemption-entities) * [Voiding redemptions](/consumer/voiding) * [Idempotency](/guides/idempotency) * [Create a redemption](/api-reference/consumer-redemptions-create) # Redemption entities Source: https://allride-voucher-consumer.mintlify.app/consumer/redemption-entities Represent the customers, employees or devices that redeem, block them, and erase them on request. A Redemption Entity is your own record of who or what redeems: an end customer, an employee, a device or an account. Entities let you apply per-entity limits, bind Redemption Links to one person, and find every redemption of one customer. ## What an entity is | Field | Meaning | | - | - | | `refId` | Your opaque identifier, unique among your entities when set. 1-128 characters of letters, digits and `._:@/+=-`, for example `customer:42` or `device:5f2c…`. Use an id from your own system, not an email address or a name: no personal data is required | | `displayName` | Optional label for your dashboard users (up to 120 characters) | | `metadata` | Optional JSON object of your own (up to 16 KiB) | | `email` | Set only for entities created by the [Email OTP Gate](/consumer/email-otp-links): the address the visitor verified. You cannot write it through the API | | `status` | `active`, `blocked` or `erased` | | `createdVia` | `api`, `dashboard`, `redemption-link-otp` or `import` | Entities belong to your partner only; another consumer can use the same `refId` for its own entity. Reading entities needs `view-consumer-redemption-entities`; creating, updating, blocking and unblocking need `manage-consumer-redemption-entities`; erasing needs `erase-consumer-redemption-entities`. All three can be granted to API keys. ## Creating entities Most integrations never create entities explicitly: they pass an inline `redemptionEntity: {"refId": "customer:42"}` when [redeeming](/consumer/redeeming#attaching-a-redemption-entity) or when creating a [Redemption Link](/consumer/redemption-links), and the platform finds or creates the entity in the same transaction. To create one up front, use [`POST /consumer/v1/redemption-entities`](/api-reference/consumer-redemption-entities-create): ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemption-entities" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: entity-customer-42" \ -d '{ "refId": "customer:42", "metadata": { "segment": "job-ticket" } }' ``` All fields are optional, so `{}` creates an anonymous entity. A `refId` that already exists answers 409 [`ref-id-taken`](/errors/catalog#ref-id-taken), and `more.redemptionEntityId` names the existing entity. Sending `email` answers 400 [`data-failed-validation`](/errors/catalog#data-failed-validation) with the constraint `unknown-field`. * Find an entity by your id with `GET /consumer/v1/redemption-entities?filter[refId]=customer:42`, or search over `refId`, `displayName` and `email` with `search`. * Change `refId`, `displayName` or `metadata` with [`PATCH`](/api-reference/consumer-redemption-entities-update). Existing redemptions keep pointing at the entity id; their history is not rewritten. * The webhook [`redemption-entity-created`](/webhooks/events/redemption-entity-created) is sent for entities created in the dashboard and by the Email OTP Gate, not for entities your API key creates. ## Blocking [`POST …/{redemptionEntityId}:block`](/api-reference/consumer-redemption-entities-block) stops an entity from redeeming, for example while you investigate misuse: ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemption-entities/9017:block" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Idempotency-Key: block-9017-2026-10-26" ``` While an entity is blocked: * redeeming with it (by id or by its `refId`) answers 409 [`redemption-entity-blocked`](/errors/catalog#redemption-entity-blocked); * Redemption Links bound to it, and email OTP verification for its address, refuse to redeem; * creating a new link for it answers the same 409. [`:unblock`](/api-reference/consumer-redemption-entities-unblock) makes it `active` again. Blocking an entity that is already blocked, or unblocking an active one, answers 409 [`invalid-state-transition`](/errors/catalog#invalid-state-transition). ## Erasure (GDPR) When a customer asks you to delete their data, call [`POST …/{redemptionEntityId}:erase`](/api-reference/consumer-redemption-entities-erase): ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemption-entities/9017:erase" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Idempotency-Key: erase-9017" ``` Erasure is immediate and irreversible. In one step the platform: * sets `refId`, `email`, `displayName` and `metadata` to `null` and the status to `erased`; * ends the entity's landing sessions and pending email codes; * revokes every `entity-bound` Redemption Link of the entity, so those links answer 410 [`redemption-link-revoked`](/errors/catalog#redemption-link-revoked) from then on. Redemptions of the entity stay in your ledger for accounting, now anonymous: they keep the entity id and their own `metadata`, which is why redemption metadata must never contain personal data. After erasure, updating, blocking, unblocking or erasing the entity again answers 409 [`redemption-entity-erased`](/errors/catalog#redemption-entity-erased); you can later create a new entity with the old `refId`. AllRide processes entity data on your behalf as a processor. The audit log records that an entity was changed or erased but never stores its `refId`, email, name or metadata. ## Related * [Redeeming vouchers](/consumer/redeeming) * [Redemption links](/consumer/redemption-links) * [Email OTP links](/consumer/email-otp-links) * [Erase a redemption entity](/api-reference/consumer-redemption-entities-erase) # Redemption links Source: https://allride-voucher-consumer.mintlify.app/consumer/redemption-links Create shareable links that let your customers pick and redeem a voucher on an AllRide-hosted, branded landing page. A Redemption Link is a URL you hand to a customer, for example in an email or your app. The customer opens it on a landing page in your branding, picks a voucher from the providers and sets you allow, and receives the code or link. You need no frontend of your own. Creating and changing links needs `manage-consumer-redemption-links`; reading them needs `view-consumer-redemption-links`. Both can be granted to API keys. ## Link modes The mode is decided when you create the link and never changes: | Mode | Created when | Who can redeem | `maxRedemptions` means | | - | - | - | - | | `entity-bound` | You send `redemptionEntityId` or an inline `redemptionEntity` | Whoever opens the link; every redemption is attributed to that [Redemption Entity](/consumer/redemption-entities) | Total redemptions for the entity through this link | | `email-otp` | You send `requireEmailOtp: true` and no entity | Visitors who verify an email address with a one-time code; the verified address becomes (or finds) an entity | Redemptions **per verified entity**; `maxTotalRedemptions` caps the whole link | | `anonymous` | Neither | Anyone with the link | Total redemptions for the link, shared by all visitors | Sending an entity together with `requireEmailOtp: true` answers 422 [`link-mode-conflict`](/errors/catalog#link-mode-conflict). An `email-otp` link needs the Email OTP Gate to be set up first, else 422 [`email-otp-not-configured`](/errors/catalog#email-otp-not-configured); see [Email OTP links](/consumer/email-otp-links). Create a link with [`POST /consumer/v1/redemption-links`](/api-reference/consumer-redemption-links-create): ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemption-links" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: job-ticket-2026-11:emp-42" \ -d '{ "name": "Job ticket November - employee 42", "redemptionEntity": { "refId": "emp-42" }, "allowedProviderIds": [1030, 1056], "allowedVoucherSetIds": [], "maxRedemptions": 1, "metadata": { "programme": "job-ticket-nov" }, "expiresAt": "2026-11-30T22:59:59Z", "defaultLocale": "de" }' ``` The 201 response contains the link with its `token`, `url`, `mode`, `status` and counters. To create many links at once, for example one entity-bound link per employee, use [`POST /consumer/v1/redemption-links:batch`](/api-reference/consumer-redemption-links-batch) with up to 100 items: it is all-or-nothing and returns a summary per link (`id`, `token`, `url`, `name`, `mode`, `redemptionEntity`, `status`). Before your first link, set the privacy and imprint URLs of your landing page in Workspace Branding; otherwise creating a link answers 422 [`landing-legal-urls-missing`](/errors/catalog#landing-legal-urls-missing). See [Landing pages and custom domains](/consumer/landing-pages). ## Restricting providers and voucher sets * `allowedProviderIds` (up to 50) limits the link to these providers. Each must have at least one visible, switched-on set in your catalog. * `allowedVoucherSetIds` (up to 200) limits the link to these sets. Each must be visible with both Catalog Toggles on; if you also send `allowedProviderIds`, each set's provider must be among them. * Empty lists mean "everything in my catalog that is switched on". Invalid ids answer 400 [`data-failed-validation`](/errors/catalog#data-failed-validation) with the constraint `provider-not-in-catalog`, `voucher-set-not-in-catalog` or `voucher-set-provider-not-allowed` for the offending list item. The restriction is checked again whenever the customer opens the link. The landing catalog shows a set only when it is `active` or `deactivation-scheduled`, both toggles are on, it is not depleted and you have a redeemable Price Acceptance for it. Sets you have not accepted prices for are silently left out, because customers never see prices. Check what a link offers right now with [`GET /consumer/v1/redemption-links/{redemptionLinkId}/catalog`](/api-reference/consumer-redemption-links-catalog): it returns the entries in landing order (at most 200) and lists in `hiddenForPriceAcceptance` the sets that are missing only because of prices. Creating a link is never blocked by missing Price Acceptances; the link offers those sets as soon as you accept their prices. ## Limits and expiry * **`maxRedemptions`** (1 or more, `null` = unlimited) caps redemptions as described per mode above. When an `anonymous` or `entity-bound` link reaches it, the link becomes `exhausted` and further redemptions answer 409 [`redemption-link-exhausted`](/errors/catalog#redemption-link-exhausted). On an `email-otp` link, a visitor who reached their own cap gets 409 [`redemption-link-entity-limit-reached`](/errors/catalog#redemption-link-entity-limit-reached) while the link stays `active`. * **`maxTotalRedemptions`** (only for `email-otp` links) caps the whole link; reaching it makes the link `exhausted`. Sending it for another mode answers 400 `data-failed-validation` with the constraint `not-allowed-for-mode`. Set it for public `email-otp` links: without it, anyone with an email address can redeem. * **`expiresAt`** (in the future) ends the link. From that moment it is `expired`, also before the status is persisted, and the landing page answers 410 [`redemption-link-expired`](/errors/catalog#redemption-link-expired). * Per-set **entity limits** (`entityLimit` in your [catalog settings](/consumer/catalog#your-own-settings-and-references)) apply on top whenever the link knows the entity. Change a link with [`PATCH`](/api-reference/consumer-redemption-links-update): `name`, the allowed lists, `maxRedemptions`, `maxTotalRedemptions`, `metadata`, `expiresAt`, `customDomainId` and `defaultLocale`. The mode, entity, `requireEmailOtp` and token are fixed. Raising the cap of an `exhausted` link or moving `expiresAt` of an `expired` link into the future makes it `active` again; lowering a cap below the current `redemptionCount` answers 422 [`max-below-count`](/errors/catalog#max-below-count); a cap equal to the count makes the link `exhausted`. [`POST …/{redemptionLinkId}:revoke`](/api-reference/consumer-redemption-links-revoke) ends a link immediately and for good: the landing page answers 410 [`redemption-link-revoked`](/errors/catalog#redemption-link-revoked), and open visitor sessions stop working. Erasing an entity revokes all of its `entity-bound` links. You receive the webhooks [`redemption-link-exhausted`](/webhooks/events/redemption-link-exhausted) and [`redemption-link-expired`](/webhooks/events/redemption-link-expired). ## Metadata forwarding `metadata` on the link is your own JSON object (up to 16 KiB). Every redemption made through the link takes a snapshot of it, and the [`redemption-created`](/webhooks/events/redemption-created) webhook carries it as `redemptionLink.metadata`, together with `redemptionEntity.refId` for entity-bound and `email-otp` links: ```json theme={null} { "event": { "id": "5b8e0d3a-2c71-4f9e-8a46-d1e7c3b90f25", "type": "redemption-created", "timestamp": 1793693564 }, "payload": { "resource": "redemption", "id": 6101, "status": "completed", "channel": "redemption-link", "voucherSetId": 2030, "redemptionEntity": { "id": 9030, "resource": "redemption-entity", "refId": "emp-42" }, "redemptionLink": { "id": 8017, "resource": "redemption-link", "metadata": { "programme": "job-ticket-nov" } } } } ``` The example shows only some fields of `payload`; see the event page for the full payload. Changing a link's `metadata` affects future redemptions only. Never put personal data in link metadata. ## Sharing the link The link's `url` is `https:///r/`. The host is the link's `customDomainId` when that domain is active, else your default landing domain, else `redeem.voucher.allride.io`. See [Custom domains](/workspace/custom-domains). The 22-character `token` is a bearer capability: anyone who has the URL can use the link within its limits. Send it only to the intended customer, over a channel you trust, and do not post `entity-bound` links publicly. If a link leaks, revoke it and create a new one. ## What the customer sees 1. The customer opens the URL and sees a landing page with your logo, colours and texts, your terms, privacy and imprint links, and the link's `defaultLocale` (German or English). 2. For an `email-otp` link, they enter their email address, receive a 6-digit code from your SMTP server and enter it. See [Email OTP links](/consumer/email-otp-links). 3. They browse the offered providers and voucher sets and pick one. 4. If you turned on bot protection in Workspace Branding (`landing.botProtection: turnstile`), the page runs a Cloudflare Turnstile check before each redemption and each code request; with the default `off`, no check is shown. 5. They receive the code or link, with the provider's redemption instructions and validity. Reloading the page shows the vouchers already issued in the visitor's session (or to their entity) again, so a customer never loses a code. On `anonymous` and `entity-bound` links the visitor's session lasts as long as the link is valid: no "session ended" message while the link can be used. Revoking the link, its expiry or blocking the entity still ends access at once. On `email-otp` links the session lasts as long as you set in [Email OTP links](/consumer/email-otp-links#session-length). An exhausted link still shows the vouchers issued earlier; an expired or revoked link shows a branded "link unavailable" page with your support contacts. ## Related * [Email OTP links](/consumer/email-otp-links) * [Landing pages and custom domains](/consumer/landing-pages) * [Redemption entities](/consumer/redemption-entities) * [Create a redemption link](/api-reference/consumer-redemption-links-create) # Voiding redemptions Source: https://allride-voucher-consumer.mintlify.app/consumer/voiding Void a redemption that should not count, within 30 days, and understand what a void changes. Voiding marks a redemption as not counting: it frees the customer's entity limit, removes the redemption from billing and tells you and the provider through webhooks. It does not return the voucher to stock. Voiding needs the permission `void-consumer-redemptions`, which can be granted to API keys. ## When to void Void a redemption when the customer did not get the benefit, for example: * your system issued the code but the order behind it was cancelled before the customer saw the code; * a support case shows the code never reached the customer, and you issue a new one; * you redeemed by mistake (wrong voucher set, test call against production). Do not void to "take back" a voucher the customer already used at the provider: the void does not invalidate the code at the provider. Call [`POST /consumer/v1/redemptions/{redemptionId}:void`](/api-reference/consumer-redemptions-void) with a reason: ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemptions/6017:void" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: void-6017" \ -d '{ "voidReason": "Order 8812 cancelled before delivery" }' ``` The response is **200** with the redemption, now with `status: voided`, `voidedAt`, `voidedBy` and `voidReason`. ## Rules * **30-day window:** you can void a redemption up to 30 days after its `redeemedAt`. Later attempts answer 422 [`void-window-expired`](/errors/catalog#void-window-expired); `more.voidableUntil` tells you when the window closed. Contact AllRide support for older cases. * **Reason required:** `voidReason` is 1-500 characters. It is visible to you and recorded in your audit log; it is never shown to the provider. Do not put personal data in it. * **Once only:** voiding a voided redemption answers 409 [`redemption-already-voided`](/errors/catalog#redemption-already-voided). With the same `Idempotency-Key` a retry replays the original 200. * **No restock:** the voucher is never returned to stock. A single-use voucher stays used; a multi-use voucher keeps its count. * **Limits:** the void frees one unit of the entity's `entityLimit` for the redemption's period and of the visitor's per-person cap on an `email-otp` link. It does not lower the total `redemptionCount` of a Redemption Link and does not re-open an exhausted link; raise the link's `maxRedemptions` for that. * **Billing:** a void before the month is invoiced removes the redemption from the next invoice. An invoice already issued is never changed by a later void; if an invoice is wrong, AllRide corrects it with a Stornorechnung and a corrected invoice (see [Corrections: Stornorechnung](/consumer/billing#corrections-stornorechnung)). See [Billing](/consumer/billing). * **Statistics:** the void counts in the statistics bucket of `voidedAt`, so a void in November of an October redemption shows in November. * **Webhooks:** you receive [`redemption-voided`](/webhooks/events/redemption-voided) with the voided redemption. The provider receives its own `redemption-voided` with `voidedAt` but without your reason, entity or metadata. * Voids are recorded in your audit log for every caller, including API keys. AllRide staff who void on your behalf appear as `AllRide` in `voidedBy`. Unknown or another partner's redemption ids answer 404 [`redemption-not-found`](/errors/catalog#redemption-not-found). ## Related * [Redeeming vouchers](/consumer/redeeming) * [Billing](/consumer/billing) * [Analytics](/consumer/analytics) * [Void a redemption](/api-reference/consumer-redemptions-void) # Error catalog Source: https://allride-voucher-consumer.mintlify.app/errors/catalog Every error code the Consumer API can return, with what to do about it. Every error response is `{"errors": [{"message", "statusCode", "errorCode", …}]}` and its `errorCode` is listed below; the [errors guide](/errors) explains the format and when to retry. ## 400 Bad Request ### `data-failed-validation` **400 Bad Request** · Data failed validation · Retry: after changing the request One or more properties or parameters are invalid; each is listed in `more.validationErrors` with its property path and violated constraint. Unknown properties and query parameters are refused as well. **What to do:** Correct every entry in `more.validationErrors`. Returned by: [Get entity statistics](/api-reference/consumer-analytics-entity-stats), [Get redemption link funnel statistics](/api-reference/consumer-analytics-link-stats), [Get the analytics overview](/api-reference/consumer-analytics-overview), [Get redemption statistics](/api-reference/consumer-analytics-redemption-stats), [Get the billing account](/api-reference/consumer-billing-account-get), [Update the billing account](/api-reference/consumer-billing-account-update), [Get the billing balance](/api-reference/consumer-billing-balance-get), [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Remove a billing contact](/api-reference/consumer-billing-contacts-delete), [List billing contacts](/api-reference/consumer-billing-contacts-list), [Update a billing contact](/api-reference/consumer-billing-contacts-update), [Download an invoice document](/api-reference/consumer-billing-invoices-document), [Get an invoice](/api-reference/consumer-billing-invoices-get), [List invoice lines](/api-reference/consumer-billing-invoices-lines), [List invoices](/api-reference/consumer-billing-invoices-list), [Resend an invoice](/api-reference/consumer-billing-invoices-resend), [Get a payment method](/api-reference/consumer-billing-payment-methods-get), [List payment methods](/api-reference/consumer-billing-payment-methods-list), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit), [List payments](/api-reference/consumer-billing-payments-list), [Get the usage estimate of a month](/api-reference/consumer-billing-usage-get), [List usage lines of a month](/api-reference/consumer-billing-usage-lines), [Get a category](/api-reference/consumer-catalog-categories-get), [List categories](/api-reference/consumer-catalog-categories-list), [Get a catalog provider](/api-reference/consumer-catalog-providers-get), [List catalog providers](/api-reference/consumer-catalog-providers-list), [Update provider toggle and settings](/api-reference/consumer-catalog-providers-update), [Get a catalog voucher set](/api-reference/consumer-catalog-voucher-sets-get), [List catalog voucher sets](/api-reference/consumer-catalog-voucher-sets-list), [Update voucher set toggle and settings](/api-reference/consumer-catalog-voucher-sets-update), [Get email OTP readiness](/api-reference/consumer-email-otp-readiness-get), [Get email OTP settings](/api-reference/consumer-email-otp-settings-get), [Update email OTP settings](/api-reference/consumer-email-otp-settings-update), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [List Price Acceptances](/api-reference/consumer-pricing-acceptances-list), [Get the pricing summary](/api-reference/consumer-pricing-summary-get), [Get the Price Terms of a voucher set](/api-reference/consumer-pricing-voucher-sets-get), [List Price Terms of visible voucher sets](/api-reference/consumer-pricing-voucher-sets-list), [Get a redemption batch](/api-reference/consumer-redemption-batches-get), [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Get a redemption entity](/api-reference/consumer-redemption-entities-get), [List redemption entities](/api-reference/consumer-redemption-entities-list), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Update a redemption entity](/api-reference/consumer-redemption-entities-update), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Preview a link's catalog](/api-reference/consumer-redemption-links-catalog), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Get a redemption link](/api-reference/consumer-redemption-links-get), [List redemption links](/api-reference/consumer-redemption-links-list), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Update a redemption link](/api-reference/consumer-redemption-links-update), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create), [Get a redemption](/api-reference/consumer-redemptions-get), [List redemptions](/api-reference/consumer-redemptions-list), [Void a redemption](/api-reference/consumer-redemptions-void), [Get the API access status](/api-reference/partner-api-access-get), [Request API access](/api-reference/partner-api-access-request), [Create an API key](/api-reference/partner-api-keys-create), [Get an API key](/api-reference/partner-api-keys-get), [List API keys](/api-reference/partner-api-keys-list), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Rename an API key](/api-reference/partner-api-keys-update), [Get an audit event](/api-reference/partner-audit-events-get), [List audit events](/api-reference/partner-audit-events-list), [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Create a brand asset](/api-reference/partner-brand-assets-create), [Delete a brand asset](/api-reference/partner-brand-assets-delete), [Get a brand asset](/api-reference/partner-brand-assets-get), [List brand assets](/api-reference/partner-brand-assets-list), [Add a custom domain](/api-reference/partner-custom-domains-create), [Delete a custom domain](/api-reference/partner-custom-domains-delete), [Get a custom domain](/api-reference/partner-custom-domains-get), [List custom domains](/api-reference/partner-custom-domains-list), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Get developer reference information](/api-reference/partner-developer-reference-get), [Get a template variable contract](/api-reference/partner-email-template-contracts-get), [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Create an email template](/api-reference/partner-email-templates-create), [Get an email template](/api-reference/partner-email-templates-get), [List email templates](/api-reference/partner-email-templates-list), [Render a saved template preview](/api-reference/partner-email-templates-preview), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Update a draft email template](/api-reference/partner-email-templates-update), [Create a group](/api-reference/partner-groups-create), [Delete a group](/api-reference/partner-groups-delete), [Get a group](/api-reference/partner-groups-get), [List groups](/api-reference/partner-groups-list), [List the members of a group](/api-reference/partner-groups-members), [Update a group](/api-reference/partner-groups-update), [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create), [Remove an IP allowlist entry](/api-reference/partner-ip-allowlist-delete), [List IP allowlist entries](/api-reference/partner-ip-allowlist-list), [Relabel an IP allowlist entry](/api-reference/partner-ip-allowlist-update), [Invite a member](/api-reference/partner-members-create), [Get a member](/api-reference/partner-members-get), [List members](/api-reference/partner-members-list), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Change a member's direct permissions](/api-reference/partner-members-update), [Get your partner](/api-reference/partner-partner-get), [Update general information](/api-reference/partner-partner-update), [Update security settings](/api-reference/partner-partner-update-settings), [List permissions](/api-reference/partner-permissions-list), [Get the calling principal](/api-reference/partner-principal-get), [Get the SMTP configuration](/api-reference/partner-smtp-configuration-get), [Create or replace the SMTP configuration](/api-reference/partner-smtp-configuration-replace), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify), [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Create an SSO connection](/api-reference/partner-sso-connections-create), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Get an SSO connection](/api-reference/partner-sso-connections-get), [List SSO connections](/api-reference/partner-sso-connections-list), [Update an SSO connection](/api-reference/partner-sso-connections-update), [Verify SSO email domains](/api-reference/partner-sso-connections-verify), [List selectable time zones](/api-reference/partner-timezones-list), [Get a webhook delivery](/api-reference/partner-webhook-deliveries-get), [List webhook deliveries](/api-reference/partner-webhook-deliveries-list), [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay), [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Delete a webhook endpoint](/api-reference/partner-webhook-endpoints-delete), [Get a webhook endpoint](/api-reference/partner-webhook-endpoints-get), [List webhook endpoints](/api-reference/partner-webhook-endpoints-list), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event), [Update a webhook endpoint](/api-reference/partner-webhook-endpoints-update), [List webhook event types](/api-reference/partner-webhook-event-types-list), [Get workspace branding](/api-reference/partner-workspace-branding-get), [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ### `invalid-header` **400 Bad Request** · Invalid header · Retry: after changing the request A standard or platform header is malformed or missing; `more.validationErrors[].property` names it. **What to do:** Correct the header named in `more.validationErrors`. Returned by: [Update the billing account](/api-reference/consumer-billing-account-update), [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Update a billing contact](/api-reference/consumer-billing-contacts-update), [Resend an invoice](/api-reference/consumer-billing-invoices-resend), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit), [Update provider toggle and settings](/api-reference/consumer-catalog-providers-update), [Update voucher set toggle and settings](/api-reference/consumer-catalog-voucher-sets-update), [Update email OTP settings](/api-reference/consumer-email-otp-settings-update), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Update a redemption entity](/api-reference/consumer-redemption-entities-update), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Update a redemption link](/api-reference/consumer-redemption-links-update), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create), [Void a redemption](/api-reference/consumer-redemptions-void), [Request API access](/api-reference/partner-api-access-request), [Create an API key](/api-reference/partner-api-keys-create), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Rename an API key](/api-reference/partner-api-keys-update), [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Create a brand asset](/api-reference/partner-brand-assets-create), [Add a custom domain](/api-reference/partner-custom-domains-create), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Create an email template](/api-reference/partner-email-templates-create), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Update a draft email template](/api-reference/partner-email-templates-update), [Create a group](/api-reference/partner-groups-create), [Update a group](/api-reference/partner-groups-update), [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create), [Relabel an IP allowlist entry](/api-reference/partner-ip-allowlist-update), [Invite a member](/api-reference/partner-members-create), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Change a member's direct permissions](/api-reference/partner-members-update), [Update general information](/api-reference/partner-partner-update), [Update security settings](/api-reference/partner-partner-update-settings), [Create or replace the SMTP configuration](/api-reference/partner-smtp-configuration-replace), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify), [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Create an SSO connection](/api-reference/partner-sso-connections-create), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Update an SSO connection](/api-reference/partner-sso-connections-update), [Verify SSO email domains](/api-reference/partner-sso-connections-verify), [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay), [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event), [Update a webhook endpoint](/api-reference/partner-webhook-endpoints-update), [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ### `invalid-idempotency-key` **400 Bad Request** · Invalid Idempotency-Key · Retry: after changing the request The `Idempotency-Key` header is not 1-255 printable ASCII characters. **What to do:** Send a valid key, for example a UUID. Returned by: [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Resend an invoice](/api-reference/consumer-billing-invoices-resend), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create), [Void a redemption](/api-reference/consumer-redemptions-void), [Request API access](/api-reference/partner-api-access-request), [Create an API key](/api-reference/partner-api-keys-create), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Create a brand asset](/api-reference/partner-brand-assets-create), [Add a custom domain](/api-reference/partner-custom-domains-create), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Create an email template](/api-reference/partner-email-templates-create), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Create a group](/api-reference/partner-groups-create), [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create), [Invite a member](/api-reference/partner-members-create), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify), [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Create an SSO connection](/api-reference/partner-sso-connections-create), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Verify SSO email domains](/api-reference/partner-sso-connections-verify), [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay), [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event) ### `malformed-json` **400 Bad Request** · Malformed JSON · Retry: after changing the request The body is not valid UTF-8 JSON or nests deeper than 32 levels. **What to do:** Send a valid JSON document. Returned by: [Update the billing account](/api-reference/consumer-billing-account-update), [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Update a billing contact](/api-reference/consumer-billing-contacts-update), [Update provider toggle and settings](/api-reference/consumer-catalog-providers-update), [Update voucher set toggle and settings](/api-reference/consumer-catalog-voucher-sets-update), [Update email OTP settings](/api-reference/consumer-email-otp-settings-update), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Update a redemption entity](/api-reference/consumer-redemption-entities-update), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Update a redemption link](/api-reference/consumer-redemption-links-update), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create), [Void a redemption](/api-reference/consumer-redemptions-void), [Request API access](/api-reference/partner-api-access-request), [Create an API key](/api-reference/partner-api-keys-create), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Rename an API key](/api-reference/partner-api-keys-update), [Create a brand asset](/api-reference/partner-brand-assets-create), [Add a custom domain](/api-reference/partner-custom-domains-create), [Create an email template](/api-reference/partner-email-templates-create), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Update a draft email template](/api-reference/partner-email-templates-update), [Create a group](/api-reference/partner-groups-create), [Update a group](/api-reference/partner-groups-update), [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create), [Relabel an IP allowlist entry](/api-reference/partner-ip-allowlist-update), [Invite a member](/api-reference/partner-members-create), [Change a member's direct permissions](/api-reference/partner-members-update), [Update general information](/api-reference/partner-partner-update), [Update security settings](/api-reference/partner-partner-update-settings), [Create or replace the SMTP configuration](/api-reference/partner-smtp-configuration-replace), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify), [Create an SSO connection](/api-reference/partner-sso-connections-create), [Update an SSO connection](/api-reference/partner-sso-connections-update), [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event), [Update a webhook endpoint](/api-reference/partner-webhook-endpoints-update), [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ## 401 Unauthorized ### `api-key-invalid` **401 Unauthorized** · Invalid API key · Retry: never The API key is unknown, revoked, expired, past its roll grace period or belongs to the other environment (`vk_test_` against production or `vk_live_` against the sandbox). **What to do:** Check the key and the base URL; create or roll a key in the developer dashboard. Returned by: any operation. ### `token-expired` **401 Unauthorized** · Credential expired · Retry: never The dashboard ID token has expired. **What to do:** Sign in again; API-key integrations never see this code. Returned by: any operation. ### `token-invalid` **401 Unauthorized** · Invalid credential · Retry: never The credential cannot be verified for this surface (wrong scheme, signature or credential kind). **What to do:** Send a valid API key for this surface. Returned by: any operation. ### `unauthenticated` **401 Unauthorized** · Authentication required · Retry: never No credential was sent with the request. **What to do:** Send your API key as `Authorization: Bearer `. Returned by: any operation. ## 403 Forbidden ### `api-access-inactive` **403 Forbidden** · API access inactive · Retry: never API access of your partner is not active (not yet granted, revoked, or the partner is suspended), so API keys are refused. **What to do:** Request API access in the developer dashboard and wait for AllRide to activate it. Returned by: any operation. ### `bot-check-failed` **403 Forbidden** · Bot check failed · Retry: after changing the request The Turnstile token of a landing-page request is missing or invalid (only when your landing `botProtection` is `turnstile`). **What to do:** The landing page retries the check; nothing to do on your side. ### `ip-not-allowed` **403 Forbidden** · IP address not allowed · Retry: never The request's source address is not in your IP allowlist while enforcement is on. **What to do:** Send from an allowlisted address or add your egress range to the allowlist. Returned by: any operation. ### `mfa-required` **403 Forbidden** · Multi-factor authentication required · Retry: never Your partner requires multi-factor authentication for dashboard users. **What to do:** Enrol a second factor or complete the step-up (dashboard users only). **Details in `more`:** `enrolUrl` (Where to enrol.), `stepUpUrl` (Where to step up.) Returned by: any operation. ### `missing-permission` **403 Forbidden** · Missing permission · Retry: after changing the request The membership or API key lacks a permission the operation requires, including non-delegable permissions called with an API key. **What to do:** Ask a partner administrator to grant the permissions in `requiredPermissions`, or use a key with that scope. **Details in `more`:** `requiredPermissions` (The permissions the operation needs.) Returned by: any operation. ### `owner-membership-required` **403 Forbidden** · Owner membership required · Retry: never Only members of the `owners` group can change owners and administrators. **What to do:** Ask an owner to make the change. Returned by: [Update a group](/api-reference/partner-groups-update), [Invite a member](/api-reference/partner-members-create), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Suspend a member](/api-reference/partner-members-suspend) ### `partner-not-active` **403 Forbidden** · Partner not active · Retry: never Your partner is still in draft or onboarding and the operation needs an active partner. **What to do:** Complete onboarding first. **Details in `more`:** `partnerStatus` (Current partner status.) ### `partner-offboarded` **403 Forbidden** · Partner offboarded · Retry: never Your partner has been offboarded; every route is refused. **What to do:** Contact AllRide. Returned by: any operation. ### `partner-role-missing` **403 Forbidden** · Partner role missing · Retry: never Your partner lacks the role (provider or consumer) this surface needs. **What to do:** Use the surface of your partner's role. **Details in `more`:** `requiredRole` (The role the surface needs.) Returned by: any operation. ### `partner-suspended` **403 Forbidden** · Partner suspended · Retry: never Your partner is suspended: writes are refused (billing writes from the dashboard excepted). **What to do:** Contact AllRide; for billing issues fix the payment method in the consumer dashboard. Returned by: any operation. ### `reauthentication-required` **403 Forbidden** · Recent sign-in required · Retry: after changing the request The operation needs a sign-in within the last 15 minutes (dashboard users only). **What to do:** Sign in again and retry. **Details in `more`:** `stepUpUrl` (Where to sign in again.) Returned by: any operation. ### `sso-required` **403 Forbidden** · Single sign-on required · Retry: never Your partner enforces single sign-on for this user's email domain. **What to do:** Sign in through your identity provider (dashboard users only). **Details in `more`:** `providerId` (The SSO connection to use.) Returned by: any operation. ## 404 Not Found ### `api-key-not-found` **404 Not Found** · API key not found · Retry: after changing the request No API key with this id exists in your partner. **What to do:** Check the id. Returned by: [Get an API key](/api-reference/partner-api-keys-get), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Rename an API key](/api-reference/partner-api-keys-update) ### `audit-event-not-found` **404 Not Found** · Audit event not found · Retry: after changing the request No audit event with this id is visible to your partner. **What to do:** Check the id. Returned by: [Get an audit event](/api-reference/partner-audit-events-get) ### `billing-account-not-found` **404 Not Found** · Billing account not found · Retry: after changing the request Your partner has no billing account (yet). **What to do:** Contact AllRide; billing accounts are created automatically for consumer partners. Returned by: [Get the billing account](/api-reference/consumer-billing-account-get), [Update the billing account](/api-reference/consumer-billing-account-update), [Get the billing balance](/api-reference/consumer-billing-balance-get), [Add a billing contact](/api-reference/consumer-billing-contacts-create), [List billing contacts](/api-reference/consumer-billing-contacts-list), [List invoices](/api-reference/consumer-billing-invoices-list), [List payment methods](/api-reference/consumer-billing-payment-methods-list), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit), [List payments](/api-reference/consumer-billing-payments-list), [Get the usage estimate of a month](/api-reference/consumer-billing-usage-get), [List usage lines of a month](/api-reference/consumer-billing-usage-lines) ### `billing-contact-not-found` **404 Not Found** · Billing contact not found · Retry: after changing the request No billing contact with this id exists on your billing account. **What to do:** Check the id; list the contacts to find it. Returned by: [Remove a billing contact](/api-reference/consumer-billing-contacts-delete), [Update a billing contact](/api-reference/consumer-billing-contacts-update) ### `brand-asset-not-found` **404 Not Found** · Brand asset not found · Retry: after changing the request No brand asset with this id exists in your partner. **What to do:** Check the id; list assets with `GET /partner/v1/brand-assets`. Returned by: [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Delete a brand asset](/api-reference/partner-brand-assets-delete), [Get a brand asset](/api-reference/partner-brand-assets-get), [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ### `category-not-found` **404 Not Found** · Category not found · Retry: after changing the request The category does not exist or is archived. **What to do:** Use an active category from `GET /consumer/v1/catalog/categories`. Returned by: [Get a category](/api-reference/consumer-catalog-categories-get), [Update provider toggle and settings](/api-reference/consumer-catalog-providers-update), [Update voucher set toggle and settings](/api-reference/consumer-catalog-voucher-sets-update) ### `custom-domain-not-found` **404 Not Found** · Custom domain not found · Retry: after changing the request The custom domain does not exist or belongs to another partner. **What to do:** Use one of your custom domains (`GET /partner/v1/custom-domains`). Returned by: [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Update a redemption link](/api-reference/consumer-redemption-links-update), [Delete a custom domain](/api-reference/partner-custom-domains-delete), [Get a custom domain](/api-reference/partner-custom-domains-get), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Update security settings](/api-reference/partner-partner-update-settings) ### `email-template-contract-not-found` **404 Not Found** · Email template contract not found · Retry: after changing the request There is no variable contract for this template purpose. **What to do:** Use the purpose `redemption-link-otp`. Returned by: [Get a template variable contract](/api-reference/partner-email-template-contracts-get) ### `email-template-not-found` **404 Not Found** · Email template not found · Retry: after changing the request The email template does not exist or belongs to another partner. **What to do:** Check the id. Returned by: [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Get an email template](/api-reference/partner-email-templates-get), [Render a saved template preview](/api-reference/partner-email-templates-preview), [Update a draft email template](/api-reference/partner-email-templates-update), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify) ### `group-not-found` **404 Not Found** · Group not found · Retry: after changing the request No group with this id exists in your partner. **What to do:** Check the id; list groups with `GET /partner/v1/groups`. Returned by: [Delete a group](/api-reference/partner-groups-delete), [Get a group](/api-reference/partner-groups-get), [List the members of a group](/api-reference/partner-groups-members), [Update a group](/api-reference/partner-groups-update) ### `invoice-not-found` **404 Not Found** · Invoice not found · Retry: after changing the request No finalised invoice with this id exists on your billing account (drafts are not visible). **What to do:** Check the id; list the invoices to find it. Returned by: [Download an invoice document](/api-reference/consumer-billing-invoices-document), [Get an invoice](/api-reference/consumer-billing-invoices-get), [List invoice lines](/api-reference/consumer-billing-invoices-lines), [Resend an invoice](/api-reference/consumer-billing-invoices-resend) ### `ip-allowlist-entry-not-found` **404 Not Found** · IP allowlist entry not found · Retry: after changing the request No IP allowlist entry with this id exists in your partner. **What to do:** Check the id. Returned by: [Remove an IP allowlist entry](/api-reference/partner-ip-allowlist-delete), [Relabel an IP allowlist entry](/api-reference/partner-ip-allowlist-update) ### `partner-membership-not-found` **404 Not Found** · Member not found · Retry: after changing the request No membership with this id exists in your partner. **What to do:** Check the id; list members with `GET /partner/v1/members`. Returned by: [Get a member](/api-reference/partner-members-get), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Change a member's direct permissions](/api-reference/partner-members-update) ### `partner-not-found` **404 Not Found** · Partner not found · Retry: after changing the request The credential names a partner that does not exist or in which the user has no active membership. **What to do:** Sign in to a partner you are a member of. ### `payment-method-not-found` **404 Not Found** · Payment method not found · Retry: after changing the request No payment method with this id exists on your billing account. **What to do:** Check the id; list the payment methods to find it. Returned by: [Get a payment method](/api-reference/consumer-billing-payment-methods-get), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove) ### `payment-not-found` **404 Not Found** · Payment not found · Retry: after changing the request No payment with this id exists on your billing account. **What to do:** Check the id; list the payments to find it. ### `provider-not-found` **404 Not Found** · Provider not found · Retry: after changing the request The provider does not exist or has no voucher set visible to you. **What to do:** Pick a provider from your catalog (`GET /consumer/v1/catalog/providers`). Returned by: [Get a catalog provider](/api-reference/consumer-catalog-providers-get), [Update provider toggle and settings](/api-reference/consumer-catalog-providers-update), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `redemption-batch-not-found` **404 Not Found** · Redemption batch not found · Retry: after changing the request The redemption batch does not exist or belongs to another partner. **What to do:** Check the id. Returned by: [Get a redemption batch](/api-reference/consumer-redemption-batches-get) ### `redemption-entity-not-found` **404 Not Found** · Redemption entity not found · Retry: after changing the request The redemption entity does not exist or belongs to another partner. **What to do:** Check the id, or redeem with an inline `redemptionEntity` (`refId`) instead. Returned by: [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Get a redemption entity](/api-reference/consumer-redemption-entities-get), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Update a redemption entity](/api-reference/consumer-redemption-entities-update), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `redemption-link-not-found` **404 Not Found** · Redemption link not found · Retry: after changing the request The Redemption Link does not exist or belongs to another partner. **What to do:** Check the id. Returned by: [Preview a link's catalog](/api-reference/consumer-redemption-links-catalog), [Get a redemption link](/api-reference/consumer-redemption-links-get), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Update a redemption link](/api-reference/consumer-redemption-links-update) ### `redemption-not-found` **404 Not Found** · Redemption not found · Retry: after changing the request The redemption does not exist or belongs to another partner. **What to do:** Check the id. Returned by: [Get a redemption](/api-reference/consumer-redemptions-get), [Void a redemption](/api-reference/consumer-redemptions-void) ### `route-not-found` **404 Not Found** · Route not found · Retry: never No operation exists for this path. **What to do:** Check the path against the API reference. Returned by: any operation. ### `smtp-configuration-not-found` **404 Not Found** · SMTP configuration not found · Retry: after changing the request No SMTP configuration has been saved yet. **What to do:** Save one with `PUT /partner/v1/smtp-configuration` first. Returned by: [Get the SMTP configuration](/api-reference/partner-smtp-configuration-get), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify) ### `sso-connection-not-found` **404 Not Found** · SSO connection not found · Retry: after changing the request No single sign-on connection with this id exists in your partner. **What to do:** Check the id. Returned by: [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Get an SSO connection](/api-reference/partner-sso-connections-get), [Update an SSO connection](/api-reference/partner-sso-connections-update), [Verify SSO email domains](/api-reference/partner-sso-connections-verify) ### `voucher-set-not-found` **404 Not Found** · Voucher set not found · Retry: after changing the request The voucher set does not exist, was never approved, or is not visible to you. **What to do:** Pick a voucher set from your catalog (`GET /consumer/v1/catalog/voucher-sets`). Returned by: [Get a catalog voucher set](/api-reference/consumer-catalog-voucher-sets-get), [Update voucher set toggle and settings](/api-reference/consumer-catalog-voucher-sets-update), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [Get the Price Terms of a voucher set](/api-reference/consumer-pricing-voucher-sets-get), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `webhook-delivery-not-found` **404 Not Found** · Webhook delivery not found · Retry: after changing the request No delivery with this id is visible to you (billing and pricing deliveries need the matching read permission). **What to do:** Check the id and your permissions. Returned by: [Get a webhook delivery](/api-reference/partner-webhook-deliveries-get), [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay) ### `webhook-endpoint-not-found` **404 Not Found** · Webhook endpoint not found · Retry: after changing the request No webhook endpoint with this id exists in your partner. **What to do:** Check the id. Returned by: [Delete a webhook endpoint](/api-reference/partner-webhook-endpoints-delete), [Get a webhook endpoint](/api-reference/partner-webhook-endpoints-get), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event), [Update a webhook endpoint](/api-reference/partner-webhook-endpoints-update) ## 405 Method Not Allowed ### `method-not-allowed` **405 Method Not Allowed** · Method not allowed · Retry: never The path exists but not with this method. **What to do:** Use a method listed in the `Allow` header. ## 409 Conflict ### `already-exists` **409 Conflict** · Already exists · Retry: after changing the request A unique value you sent is already used. **What to do:** Change the conflicting value. ### `billing-account-closed` **409 Conflict** · Billing account closed · Retry: never The billing account is closed; it accepts no further changes or payment methods. **What to do:** Contact AllRide. Returned by: [Update the billing account](/api-reference/consumer-billing-account-update), [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Remove a billing contact](/api-reference/consumer-billing-contacts-delete), [Update a billing contact](/api-reference/consumer-billing-contacts-update), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit) ### `billing-contact-exists` **409 Conflict** · Billing contact exists · Retry: after changing the request A billing contact with this email address already exists on the account. **What to do:** Update the existing contact instead, or use another email address. Returned by: [Add a billing contact](/api-reference/consumer-billing-contacts-create) ### `brand-asset-in-use` **409 Conflict** · Brand asset in use · Retry: after changing the request The asset is still referenced by branding, content or an active or draft email template. **What to do:** Remove the references listed in `references`, then delete again. **Details in `more`:** `references` (Up to 20 places that use the asset: `{type, id, pointer}`.) Returned by: [Delete a brand asset](/api-reference/partner-brand-assets-delete) ### `concurrent-update` **409 Conflict** · Concurrent update · Retry: with backoff A database conflict persisted after the internal retries. Nothing was stored for your `Idempotency-Key`. **What to do:** Retry with the same `Idempotency-Key` after a short backoff. ### `domain-taken` **409 Conflict** · Domain taken · Retry: after changing the request The host name is already registered on the platform. **What to do:** Use another host name, or delete it where it is registered. Returned by: [Add a custom domain](/api-reference/partner-custom-domains-create) ### `email-template-in-use` **409 Conflict** · Email template in use · Retry: after changing the request The active template cannot be archived while email OTP links are enabled. **What to do:** Activate another template, or disable email OTP links first. Returned by: [Archive an email template](/api-reference/partner-email-templates-archive) ### `email-template-not-editable` **409 Conflict** · Email template not editable · Retry: after changing the request Only `draft` templates can be edited. **What to do:** Create a new draft, edit it and activate it. Returned by: [Update a draft email template](/api-reference/partner-email-templates-update) ### `entity-limit-reached` **409 Conflict** · Entity limit reached · Retry: after changing the request The redemption entity has reached the per-entity limit you configured for the voucher set (`entityLimit`). **What to do:** Do not retry for this entity in this period; raise the limit in the catalog settings if intended. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `external-ref-taken` **409 Conflict** · External reference taken · Retry: after changing the request Another provider or voucher set setting of your partner already uses this `externalRef`. **What to do:** Choose a unique `externalRef`. Returned by: [Update provider toggle and settings](/api-reference/consumer-catalog-providers-update), [Update voucher set toggle and settings](/api-reference/consumer-catalog-voucher-sets-update) ### `group-in-use` **409 Conflict** · Group in use · Retry: after changing the request The group is the default group of a single sign-on connection. **What to do:** Change the connection's `jitDefaultGroupId` first. Returned by: [Delete a group](/api-reference/partner-groups-delete) ### `group-name-taken` **409 Conflict** · Group name taken · Retry: after changing the request An active group with this name already exists (names are compared case-insensitively). **What to do:** Choose another name. Returned by: [Create a group](/api-reference/partner-groups-create), [Update a group](/api-reference/partner-groups-update) ### `idempotency-key-in-use` **409 Conflict** · Idempotency-Key in use · Retry: after the Retry-After delay A request with the same `Idempotency-Key` is still being processed. **What to do:** Retry with the same key after the `Retry-After` interval. Returned by: [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Resend an invoice](/api-reference/consumer-billing-invoices-resend), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create), [Void a redemption](/api-reference/consumer-redemptions-void), [Request API access](/api-reference/partner-api-access-request), [Create an API key](/api-reference/partner-api-keys-create), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Create a brand asset](/api-reference/partner-brand-assets-create), [Add a custom domain](/api-reference/partner-custom-domains-create), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Create an email template](/api-reference/partner-email-templates-create), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Create a group](/api-reference/partner-groups-create), [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create), [Invite a member](/api-reference/partner-members-create), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify), [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Create an SSO connection](/api-reference/partner-sso-connections-create), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Verify SSO email domains](/api-reference/partner-sso-connections-verify), [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay), [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event) ### `invalid-state-transition` **409 Conflict** · Invalid state transition · Retry: after changing the request The resource's current status does not allow this command. **What to do:** Re-read the resource and act on its current status. Returned by: [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Update a redemption link](/api-reference/consumer-redemption-links-update), [Request API access](/api-reference/partner-api-access-request), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Rename an API key](/api-reference/partner-api-keys-update), [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Update a group](/api-reference/partner-groups-update), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Change a member's direct permissions](/api-reference/partner-members-update), [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Verify SSO email domains](/api-reference/partner-sso-connections-verify), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Update a webhook endpoint](/api-reference/partner-webhook-endpoints-update) ### `invoice-document-not-ready` **409 Conflict** · Invoice document not ready · Retry: after the Retry-After delay The PDF or XML document of this invoice is still being rendered. **What to do:** Retry after the `Retry-After` interval (30 seconds). Returned by: [Download an invoice document](/api-reference/consumer-billing-invoices-document), [Resend an invoice](/api-reference/consumer-billing-invoices-resend) ### `ip-allowlist-entry-exists` **409 Conflict** · IP allowlist entry exists · Retry: after changing the request This CIDR block is already in your allowlist. **What to do:** Use the existing entry. Returned by: [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create) ### `ip-allowlist-required` **409 Conflict** · IP allowlist required · Retry: after changing the request Enforcement needs at least one allowlist entry: enforcement cannot be turned on with no entries, and the last entry cannot be deleted while enforcement is on. **What to do:** Add entries before enabling enforcement, or turn enforcement off before deleting the last entry. Returned by: [Remove an IP allowlist entry](/api-reference/partner-ip-allowlist-delete), [Update security settings](/api-reference/partner-partner-update-settings) ### `landing-legal-urls-required` **409 Conflict** · Landing legal URLs required · Retry: after changing the request The change would clear `landing.privacyUrl` or `landing.imprintUrl` while you still have Redemption Links that are not revoked. **What to do:** Keep both URLs, or revoke the links first. **Details in `more`:** `nonRevokedLinkCount` (Number of Redemption Links that are not revoked.) Returned by: [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ### `last-owner` **409 Conflict** · Last owner · Retry: after changing the request The operation would leave your partner without an active owner. **What to do:** Make another member an owner first. Returned by: [Update a group](/api-reference/partner-groups-update), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Suspend a member](/api-reference/partner-members-suspend) ### `membership-exists` **409 Conflict** · Membership exists · Retry: after changing the request The email already has a membership in your partner that is not removed. **What to do:** Use the existing membership, or remove it before inviting again. Returned by: [Invite a member](/api-reference/partner-members-create) ### `price-acceptance-required` **409 Conflict** · Price acceptance required · Retry: after changing the request You have not accepted the Price Terms in force at the redemption instant, or the voucher set has no price yet. **What to do:** Accept the Price Terms on the Prices page of the consumer dashboard, then retry with a new `Idempotency-Key`. **Details in `more`:** `voucherSetId` (The voucher set that needs a Price Acceptance.), `priceAcceptanceStatus` (Why the set is not redeemable.) Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `price-terms-changed` **409 Conflict** · Price terms changed · Retry: after changing the request The Price Terms of at least one item changed while you were deciding: its `termsDigest` or `periodStartsAt` no longer matches. Nothing was recorded; `more.validationErrors` names each failing item and `more` carries its `currentTermsDigest`. **What to do:** Re-read the Price Terms of the voucher set and accept the current terms. Returned by: [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create) ### `provider-depleted` **409 Conflict** · Provider depleted · Retry: after changing the request Every redeemable voucher set of the provider is depleted. **What to do:** Choose another provider or try again later with a new `Idempotency-Key`. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `redemption-already-voided` **409 Conflict** · Redemption already voided · Retry: never The redemption has already been voided. **What to do:** Nothing to do; read the redemption for its void details. Returned by: [Void a redemption](/api-reference/consumer-redemptions-void) ### `redemption-entity-blocked` **409 Conflict** · Redemption entity blocked · Retry: after changing the request The redemption entity is blocked. **What to do:** Unblock the entity (`:unblock`) or redeem for another entity. Returned by: [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `redemption-entity-erased` **409 Conflict** · Redemption entity erased · Retry: after changing the request The redemption entity has been erased; erasure is irreversible. **What to do:** Create a new entity if the person returns. Returned by: [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Update a redemption entity](/api-reference/consumer-redemption-entities-update), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `redemption-link-entity-limit-reached` **409 Conflict** · Redemption link entity limit reached · Retry: never The verified entity has reached the per-entity cap of an email OTP link. Returned to the landing page, not by the Consumer API. **What to do:** Raise `maxRedemptions` on the link if intended. ### `redemption-link-exhausted` **409 Conflict** · Redemption link exhausted · Retry: never The Redemption Link has reached its redemption cap. Returned to the landing page, not by the Consumer API. **What to do:** Raise `maxRedemptions` (or `maxTotalRedemptions`) with `PATCH /consumer/v1/redemption-links/{redemptionLinkId}`, or create a new link. ### `ref-id-taken` **409 Conflict** · refId taken · Retry: after changing the request Another redemption entity of your partner already uses this `refId`. **What to do:** Use the existing entity (its id is in `redemptionEntityId`) or choose another `refId`. **Details in `more`:** `redemptionEntityId` (Id of the entity that already has this `refId`.) Returned by: [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Update a redemption entity](/api-reference/consumer-redemption-entities-update) ### `reference-not-found` **409 Conflict** · Reference not found · Retry: after changing the request A resource the request refers to disappeared concurrently. **What to do:** Re-read the referenced resource and retry with a new `Idempotency-Key`. ### `self-modification-not-allowed` **409 Conflict** · Self-modification not allowed · Retry: after changing the request You cannot change your own permissions, groups or status. **What to do:** Ask another administrator to make the change. Returned by: [Create a group](/api-reference/partner-groups-create), [Update a group](/api-reference/partner-groups-update), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Change a member's direct permissions](/api-reference/partner-members-update) ### `sepa-not-enabled` **409 Conflict** · SEPA Direct Debit not enabled · Retry: never AllRide collects your invoices by bank transfer, so no SEPA Direct Debit mandate can be set up. AllRide decides the payment method (`preferredPaymentMethodType` of the billing account). **What to do:** Pay by bank transfer, or contact AllRide if you want to pay by SEPA Direct Debit. Returned by: [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit) ### `smtp-verification-in-progress` **409 Conflict** · SMTP verification in progress · Retry: with backoff Another SMTP verification of your partner is still running. **What to do:** Wait until `verificationCompletedAt` is set, then retry. Returned by: [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify) ### `sso-connection-exists` **409 Conflict** · SSO connection exists · Retry: after changing the request Your partner already has a single sign-on connection that is not disabled. **What to do:** Update or deactivate the existing connection. Returned by: [Create an SSO connection](/api-reference/partner-sso-connections-create) ### `sso-domain-unverified` **409 Conflict** · SSO domain unverified · Retry: after changing the request The connection has no verified email domain. **What to do:** Publish the TXT record and call `:verify` first. Returned by: [Activate an SSO connection](/api-reference/partner-sso-connections-activate) ### `system-group-immutable` **409 Conflict** · System group immutable · Retry: after changing the request System groups cannot be renamed or deleted, and the `owners` permissions are computed. **What to do:** Create a custom group instead. Returned by: [Delete a group](/api-reference/partner-groups-delete), [Update a group](/api-reference/partner-groups-update) ### `upload-file-missing` **409 Conflict** · Upload file missing · Retry: after changing the request No file was uploaded to the upload target yet. **What to do:** PUT the file to `uploadTarget.url` first, then complete the upload. Returned by: [Complete a brand asset upload](/api-reference/partner-brand-assets-complete) ### `voucher-already-redeemed` **409 Conflict** · Voucher already redeemed · Retry: after changing the request A safety check found the voucher already redeemed. This is not expected in normal operation. **What to do:** Retry with a new `Idempotency-Key`; contact support with the `X-Request-Id` if it repeats. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `voucher-set-depleted` **409 Conflict** · Voucher set depleted · Retry: after changing the request No voucher of the set can be issued: every voucher is redeemed, expired or revoked (in a batch: fewer distinct vouchers than requested). **What to do:** Choose another voucher set, or redeem by `providerId` to let the platform pick a set with stock. Use a new `Idempotency-Key` for the new attempt. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `voucher-set-unavailable` **409 Conflict** · Voucher set unavailable · Retry: after changing the request The voucher set cannot be redeemed now: it is not active, its provider is not active, AllRide no longer grants you access, or your provider or voucher set toggle is off. **What to do:** Re-read the catalog entry; choose another voucher set or turn the toggles on. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ### `webhook-endpoint-not-active` **409 Conflict** · Webhook endpoint not active · Retry: after changing the request The endpoint is paused or deleted. **What to do:** Resume the endpoint first. Returned by: [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event) ## 410 Gone ### `api-version-sunset` **410 Gone** · API version sunset · Retry: never The operation or API version is past its Sunset date. **What to do:** Move to the operation in `successorUrl`. **Details in `more`:** `sunsetAt` (Sunset date.), `successorUrl` (The successor.) ### `redemption-link-expired` **410 Gone** · Redemption link expired · Retry: never The Redemption Link has passed its `expiresAt`. Returned to the landing page, not by the Consumer API. **What to do:** Move `expiresAt` into the future (or clear it) with a PATCH, or create a new link. ### `redemption-link-revoked` **410 Gone** · Redemption link revoked · Retry: never The Redemption Link was revoked (by you, or because its redemption entity was erased). Returned to the landing page, not by the Consumer API. **What to do:** Create a new link; revocation is final. ### `webhook-event-expired` **410 Gone** · Webhook event expired · Retry: never The delivery's payload was removed because its Redemption Entity was erased (GDPR), so the event can no longer be sent. Deliveries older than 30 days are no longer in the log and answer 404 `webhook-delivery-not-found`. **What to do:** Re-read the resource through the API instead. Returned by: [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay) ## 422 Unprocessable Content ### `apex-domain-not-supported` **422 Unprocessable Content** · Apex domain not supported · Retry: after changing the request Only subdomains (at least three labels, such as `vouchers.example-transit.example.com`) can be added. **What to do:** Use a subdomain. Returned by: [Add a custom domain](/api-reference/partner-custom-domains-create) ### `api-key-limit-reached` **422 Unprocessable Content** · API key limit reached · Retry: after changing the request Your partner already has 20 active API keys. **What to do:** Revoke keys you no longer use. Returned by: [Create an API key](/api-reference/partner-api-keys-create) ### `batch-too-large` **422 Unprocessable Content** · Batch too large · Retry: after changing the request The batch asks for more than 10 vouchers in total (the sum of `quantity`). **What to do:** Split the request into batches of at most 10 vouchers. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch) ### `brand-asset-limit-reached` **422 Unprocessable Content** · Brand asset limit reached · Retry: after changing the request Your partner already has 500 counted brand assets (ready assets plus uploads younger than one hour). **What to do:** Delete unused assets, then retry. Returned by: [Create a brand asset](/api-reference/partner-brand-assets-create) ### `constraint-violated` **422 Unprocessable Content** · Constraint violated · Retry: after changing the request The request violates a data constraint. **What to do:** Correct the request. ### `content-blocked` **422 Unprocessable Content** · Content blocked · Retry: after changing the request A text you saved breaks a content rule. Each `more.validationErrors` entry names the field (`property`) and the public reason (`constraint`). **What to do:** Change the text named in `more.validationErrors` and save again. Returned by: [Create or replace the SMTP configuration](/api-reference/partner-smtp-configuration-replace), [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ### `custom-domain-limit-reached` **422 Unprocessable Content** · Custom domain limit reached · Retry: after changing the request Your partner already has 10 custom domains that are not deleted. **What to do:** Delete a domain you no longer use. Returned by: [Add a custom domain](/api-reference/partner-custom-domains-create) ### `domain-not-allowed` **422 Unprocessable Content** · Domain not allowed · Retry: after changing the request Host names under the platform domain cannot be added. **What to do:** Use a host name under a domain you own. Returned by: [Add a custom domain](/api-reference/partner-custom-domains-create) ### `email-otp-not-configured` **422 Unprocessable Content** · Email OTP not configured · Retry: after changing the request The Email OTP Gate is not ready: it is turned off, the SMTP configuration is not verified, or no email template is active. `missing` lists what is missing. **What to do:** Fix every item in `missing` (see the email OTP links guide), then retry. **Details in `more`:** `missing` (What blocks the gate.) Returned by: [Update email OTP settings](/api-reference/consumer-email-otp-settings-update), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create) ### `idempotency-key-reused` **422 Unprocessable Content** · Idempotency-Key reused · Retry: after changing the request The `Idempotency-Key` was already used for a different request. **What to do:** Use a new key for a new request. Returned by: [Add a billing contact](/api-reference/consumer-billing-contacts-create), [Resend an invoice](/api-reference/consumer-billing-invoices-resend), [Make a payment method the default](/api-reference/consumer-billing-payment-methods-make-default), [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit), [Accept Price Terms](/api-reference/consumer-pricing-acceptances-create), [Block a redemption entity](/api-reference/consumer-redemption-entities-block), [Create a redemption entity](/api-reference/consumer-redemption-entities-create), [Erase a redemption entity (GDPR)](/api-reference/consumer-redemption-entities-erase), [Unblock a redemption entity](/api-reference/consumer-redemption-entities-unblock), [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Revoke a redemption link](/api-reference/consumer-redemption-links-revoke), [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create), [Void a redemption](/api-reference/consumer-redemptions-void), [Request API access](/api-reference/partner-api-access-request), [Create an API key](/api-reference/partner-api-keys-create), [Revoke an API key](/api-reference/partner-api-keys-revoke), [Roll an API key](/api-reference/partner-api-keys-roll), [Complete a brand asset upload](/api-reference/partner-brand-assets-complete), [Create a brand asset](/api-reference/partner-brand-assets-create), [Add a custom domain](/api-reference/partner-custom-domains-create), [Re-check a custom domain](/api-reference/partner-custom-domains-refresh), [Activate an email template](/api-reference/partner-email-templates-activate), [Archive an email template](/api-reference/partner-email-templates-archive), [Create an email template](/api-reference/partner-email-templates-create), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Create a group](/api-reference/partner-groups-create), [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create), [Invite a member](/api-reference/partner-members-create), [Remove a member or revoke an invitation](/api-reference/partner-members-remove), [Resend an invitation](/api-reference/partner-members-resend-invitation), [Suspend a member](/api-reference/partner-members-suspend), [Unsuspend a member](/api-reference/partner-members-unsuspend), [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify), [Activate an SSO connection](/api-reference/partner-sso-connections-activate), [Create an SSO connection](/api-reference/partner-sso-connections-create), [Deactivate an SSO connection](/api-reference/partner-sso-connections-deactivate), [Verify SSO email domains](/api-reference/partner-sso-connections-verify), [Replay a webhook delivery](/api-reference/partner-webhook-deliveries-replay), [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Pause a webhook endpoint](/api-reference/partner-webhook-endpoints-pause), [Replay failed deliveries of an endpoint](/api-reference/partner-webhook-endpoints-replay-failed), [Resume a webhook endpoint](/api-reference/partner-webhook-endpoints-resume), [Rotate the first signing secret](/api-reference/partner-webhook-endpoints-rotate-first-secret), [Rotate the second signing secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event) ### `insufficient-contrast` **422 Unprocessable Content** · Insufficient contrast · Retry: after changing the request A text and background colour pair does not reach WCAG AA contrast (4.5:1). **What to do:** Choose colours with more contrast; `more.validationErrors[].property` names the foreground colour. Returned by: [Replace workspace branding](/api-reference/partner-workspace-branding-replace) ### `ip-allowlist-limit-reached` **422 Unprocessable Content** · IP allowlist limit reached · Retry: after changing the request Your partner already has 100 IP allowlist entries. **What to do:** Merge ranges or remove entries you no longer use. Returned by: [Add an IP allowlist entry](/api-reference/partner-ip-allowlist-create) ### `ip-allowlist-would-lock-out` **422 Unprocessable Content** · IP allowlist would lock you out · Retry: after changing the request The change would exclude your own current IP address under `api-keys-and-users` enforcement. **What to do:** Add your current egress range first. Returned by: [Remove an IP allowlist entry](/api-reference/partner-ip-allowlist-delete), [Update security settings](/api-reference/partner-partner-update-settings) ### `landing-legal-urls-missing` **422 Unprocessable Content** · Landing legal URLs missing · Retry: after changing the request Your Workspace Branding has no `landing.privacyUrl` or `landing.imprintUrl`, which landing pages must show. **What to do:** Set both URLs in the landing settings of your Workspace Branding, then retry. Returned by: [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create), [Update a redemption link](/api-reference/consumer-redemption-links-update) ### `link-mode-conflict` **422 Unprocessable Content** · Link mode conflict · Retry: after changing the request A Redemption Link cannot both be bound to a redemption entity and require the email OTP gate. **What to do:** Send either a redemption entity or `requireEmailOtp: true`, not both. Returned by: [Create redemption links in bulk](/api-reference/consumer-redemption-links-batch), [Create a redemption link](/api-reference/consumer-redemption-links-create) ### `max-below-count` **422 Unprocessable Content** · Maximum below count · Retry: after changing the request The new cap is lower than the number of redemptions already made through the link. **What to do:** Set the cap to at least the link's `redemptionCount`. Returned by: [Update a redemption link](/api-reference/consumer-redemption-links-update) ### `private-key-not-allowed` **422 Unprocessable Content** · Private key not allowed · Retry: after changing the request The request contains a private key. Only public material (a public certificate) is accepted; the payload was discarded. **What to do:** Send only the public certificate and keep the private key on your side. Returned by: [Create an SSO connection](/api-reference/partner-sso-connections-create), [Update an SSO connection](/api-reference/partner-sso-connections-update) ### `scope-not-delegable` **422 Unprocessable Content** · Scope not delegable · Retry: after changing the request A requested scope is a permission that API keys can never hold (for example `manage-api-keys`, `manage-consumer-billing`, `accept-consumer-pricing`). **What to do:** Remove the scope; that operation stays dashboard-only. Returned by: [Create an API key](/api-reference/partner-api-keys-create) ### `slug-immutable` **422 Unprocessable Content** · Slug immutable · Retry: after changing the request The partner slug cannot be changed. **What to do:** Remove `slug` from the request; contact AllRide if the slug must change. Returned by: [Update general information](/api-reference/partner-partner-update) ### `smtp-host-not-allowed` **422 Unprocessable Content** · SMTP host not allowed · Retry: after changing the request The SMTP host resolves to a private, loopback, link-local, CGNAT or metadata address, or does not resolve (item code `unresolvable`). **What to do:** Use a publicly reachable SMTP host. Returned by: [Create or replace the SMTP configuration](/api-reference/partner-smtp-configuration-replace) ### `smtp-test-recipient-missing` **422 Unprocessable Content** · SMTP test recipient missing · Retry: after changing the request An API key requested an SMTP test email, but your partner has no technical contact email to send it to. **What to do:** Set `contacts.technicalEmail` on your partner, or verify from the dashboard. Returned by: [Verify SMTP and send a test email](/api-reference/partner-smtp-configuration-verify) ### `template-invalid` **422 Unprocessable Content** · Template invalid · Retry: after changing the request The template uses a forbidden construct (triple braces, partials, decorators, dotted paths), exceeds 100 KB, or cannot be activated because it misses required variables, uses unknown ones or has a blocking finding. Each issue is listed in `more.validationErrors`. **What to do:** Fix every entry in `more.validationErrors` and retry. Returned by: [Activate an email template](/api-reference/partner-email-templates-activate), [Create an email template](/api-reference/partner-email-templates-create), [Render an unsaved template preview](/api-reference/partner-email-templates-preview-draft), [Update a draft email template](/api-reference/partner-email-templates-update) ### `upload-size-mismatch` **422 Unprocessable Content** · Upload size mismatch · Retry: after changing the request The uploaded file's size differs from `sizeBytes`. **What to do:** Create a new asset with the correct size and upload again. Returned by: [Complete a brand asset upload](/api-reference/partner-brand-assets-complete) ### `void-window-expired` **422 Unprocessable Content** · Void window expired · Retry: never The redemption is older than 30 days and can no longer be voided by you. **What to do:** Contact AllRide support if the redemption must still be voided. **Details in `more`:** `voidableUntil` (When the void window closed.) Returned by: [Void a redemption](/api-reference/consumer-redemptions-void) ### `webhook-endpoint-limit-reached` **422 Unprocessable Content** · Webhook endpoint limit reached · Retry: after changing the request Your partner already has 10 webhook endpoints that are not deleted. **What to do:** Delete an endpoint you no longer use, or add event types to an existing one. Returned by: [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create) ### `webhook-url-not-allowed` **422 Unprocessable Content** · Webhook URL not allowed · Retry: after changing the request The URL is not an https URL on port 443 or 8443 that resolves to a public address. **What to do:** Use a public https URL on port 443 or 8443. **Details in `more`:** `reason` (Why the URL was refused.) Returned by: [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create), [Update a webhook endpoint](/api-reference/partner-webhook-endpoints-update) ## 500 Internal Server Error ### `internal-error` **500 Internal Server Error** · Internal error · Retry: with backoff An unexpected failure occurred. **What to do:** Retry later with the same `Idempotency-Key`; quote the `requestId` when contacting support. Returned by: any operation. ### `link-render-failed` **500 Internal Server Error** · Link render failed · Retry: with backoff The voucher set's link template could not be rendered for the allocated voucher. No voucher was consumed. **What to do:** Retry later with a new `Idempotency-Key`; AllRide is alerted automatically. Returned by: [Redeem several vouchers in one request](/api-reference/consumer-redemptions-batch), [Redeem a voucher](/api-reference/consumer-redemptions-create) ## 502 Bad Gateway ### `upstream-error` **502 Bad Gateway** · Upstream error · Retry: with backoff A service the platform depends on answered with an error. **What to do:** Retry with backoff and the same `Idempotency-Key`. Returned by: [Create an SSO connection](/api-reference/partner-sso-connections-create), [Update an SSO connection](/api-reference/partner-sso-connections-update) ### `upstream-unavailable` **502 Bad Gateway** · Upstream unavailable · Retry: with backoff A service the platform depends on could not be reached. **What to do:** Retry with backoff and the same `Idempotency-Key`. ## 503 Service Unavailable ### `bot-check-unavailable` **503 Service Unavailable** · Bot check unavailable · Retry: after the Retry-After delay The bot check service could not be reached, so the landing request was refused (fail closed). **What to do:** Retry after the `Retry-After` interval. ### `db-unavailable` **503 Service Unavailable** · Database unavailable · Retry: after the Retry-After delay The database is overloaded or unreachable. **What to do:** Retry after the `Retry-After` interval. Returned by: any operation. ### `psp-unavailable` **503 Service Unavailable** · Payment provider unavailable · Retry: after the Retry-After delay The payment service provider could not be reached. **What to do:** Retry after the `Retry-After` interval. Returned by: [Remove a payment method](/api-reference/consumer-billing-payment-methods-remove), [Start a SEPA Direct Debit mandate setup](/api-reference/consumer-billing-payment-methods-setup-sepa-debit) ### `query-timeout` **503 Service Unavailable** · Query timeout · Retry: after the Retry-After delay A database statement took too long. **What to do:** Retry after the `Retry-After` interval; narrow list filters. Returned by: any operation. ### `service-unavailable` **503 Service Unavailable** · Service unavailable · Retry: after the Retry-After delay The service is overloaded. **What to do:** Retry after the `Retry-After` interval. Returned by: any operation. ### `upstream-circuit-open` **503 Service Unavailable** · Upstream circuit open · Retry: after the Retry-After delay A dependency is temporarily cut off after repeated failures. **What to do:** Retry after the `Retry-After` interval. ## 504 Gateway Timeout ### `upstream-timeout` **504 Gateway Timeout** · Upstream timeout · Retry: with backoff A service the platform depends on did not answer in time. **What to do:** Retry with backoff and the same `Idempotency-Key`. Returned by: [Create an SSO connection](/api-reference/partner-sso-connections-create), [Update an SSO connection](/api-reference/partner-sso-connections-update) ## Validation item codes Values of `more.validationErrors[].constraint` in a `data-failed-validation` (and `content-blocked`, `template-invalid` or batch) error. ### `admin-only-field` Only AllRide can change this property. ### `billing-contact-role-required` A billing contact needs at least one role. ### `brand-asset-not-usable` The brand asset is not ready or has the wrong kind for this field. ### `category-not-active` This category is not active. ### `content-encoded-data` Contains encoded data (base64, hex or URL-encoded payloads). ### `content-hidden-characters` Contains invisible or direction-changing characters. ### `content-impersonation` Looks like another brand's name or uses look-alike characters. ### `content-markup-not-allowed` Contains HTML, scripts or template code. ### `content-suspicious-text` Asks for passwords, PINs or payment details, or is otherwise suspicious. ### `content-too-large` The content is too large to check. ### `content-unsafe-link` Contains a link that is not allowed. ### `custom-domain-not-usable` This custom domain is not active for redemption landing pages. ### `duplicate-items` Must not contain duplicate items. ### `filter-required` One of these filters is required: \{filters}. ### `invalid-cidr` Must be an allowed IPv4 or IPv6 CIDR block in canonical form. ### `invalid-color` Must be a colour in the form #rrggbb. ### `invalid-country` Must be an ISO 3166-1 alpha-2 country code. ### `invalid-date` Must be a date in the form YYYY-MM-DD. ### `invalid-email` Must be a valid email address. ### `invalid-enum-value` Must be one of: \{allowed}. ### `invalid-filter` This filter field, operator or value is not supported by the operation, the filtered range is longer than the operation allows (`params.maxMonths` or `params.maxDays`), or more than 20 filters or 100 values in one `in` filter were sent (`params.max`). ### `invalid-format` Does not match the format \{format}. ### `invalid-hostname` Must be a valid host name. ### `invalid-i18n-text` Must be a text per supported locale, including your partner's default locale, within the length limit. ### `invalid-id` Must be a positive integer id. ### `invalid-markdown` Must be valid text in the allowed Markdown subset, including your partner's default locale. ### `invalid-metadata` Must be a JSON object of at most 16 KiB, depth 5 and 100 keys per object. ### `invalid-money` Must be an amount in cent (a non-negative integer). ### `invalid-month` Must be a month in the form YYYY-MM. ### `invalid-page` Must be between \{min} and \{max} (`page` 1-100,000, `perPage` 1-100). ### `invalid-period` Must be lifetime or calendar-month. ### `invalid-ref-id` Must be 1-128 characters of letters, digits and .\_:@/+=- ### `invalid-slug` Must be 2-40 lower-case letters, digits or hyphens and not a reserved name. ### `invalid-sort` Must be one of the sortable fields in \{allowed}, optionally prefixed with - for descending; one field only. ### `invalid-timestamp` Must be an RFC 3339 timestamp with an offset, for example 2026-11-01T00:00:00Z. ### `invalid-timezone` Must be an IANA time zone such as Europe/Berlin. ### `invalid-type` Must be of type \{expected}. ### `invalid-unit-price` Must be a unit price between 0 and 100000000 ten-thousandths with scale 4. ### `invalid-url` Must be an https URL on an allowed host. ### `invalid-vat-id` Must be a valid VAT id. ### `mutually-exclusive` Send only one of \{fields}. ### `not-allowed-for-mode` This property is not allowed for the selected mode. ### `not-in-future` Must be in the future. ### `null-not-allowed` This property must not be null. ### `out-of-range` The value is outside the allowed range. ### `permission-not-applicable` This permission does not apply to your partner's roles. ### `provider-not-in-catalog` This provider is not in your catalog. ### `range-too-large` The requested range is too large. ### `required` This property or parameter is required. ### `security-port-mismatch` The port does not match the selected TLS mode (465 implicit TLS, 587 or 2525 STARTTLS). ### `sso-domain-not-allowed` Public mailbox domains and the platform domain cannot be used for single sign-on. ### `too-few-items` Must contain at least \{min} items. ### `too-large` Must be at most \{max}. ### `too-long` Must be at most \{max} characters. ### `too-many-items` Must contain at most \{max} items. ### `too-short` Must be at least \{min} characters. ### `too-small` Must be at least \{min}. ### `unknown-field` This property is not allowed. ### `unknown-parameter` This query parameter is not supported by the operation. ### `unresolvable` The host name does not resolve. ### `unsupported-locale` Must be a supported locale (de, en). ### `vat-id-country-mismatch` The VAT id does not match the address country. ### `voucher-set-not-in-catalog` This voucher set is not in your catalog. ### `voucher-set-provider-not-allowed` This voucher set's provider is not allowed for the link. ### `webhook-event-type-not-allowed` This event type is not available to your partner or needs a permission you lack. # Errors Source: https://allride-voucher-consumer.mintlify.app/errors/index The error format, status codes, validation errors and which errors to retry. When a request fails, the API answers with a `4xx` or `5xx` status and a JSON body that names a stable, machine-readable `errorCode`. Branch on `errorCode`, not on the message text. Every code is listed with its meaning and the fix in the [error catalog](/errors/catalog). ## Error format Error responses have `Content-Type: application/json` and this body: ```json theme={null} { "errors": [ { "message": "Data failed validation", "statusCode": 400, "errorCode": "data-failed-validation", "more": { "validationErrors": [ { "property": "items[1].quantity", "constraint": "too-large", "message": { "en": "Must be at most 10.", "de": "Darf höchstens 10 sein." } } ] } } ] } ``` | Member | Always | Meaning | | - | - | - | | `errors` | yes | Array of errors. In v1 it holds exactly one entry | | `errors[0].message` | yes | Human-readable explanation. Do not parse it; it can change | | `errors[0].statusCode` | yes | The HTTP status | | `errors[0].errorCode` | yes | Stable kebab-case code, the value your code branches on | | `errors[0].more` | for some codes | Details for the code, for example `requiredPermissions` on `missing-permission` or `validationErrors` on `data-failed-validation`. The catalog lists the members per code | | `errors[0].requestId` | `5xx` only | The request id, also sent as the `X-Request-Id` header | Server errors never contain internal details. Their message is always the same: ```json theme={null} { "errors": [ { "message": "Something went wrong on our side", "statusCode": 500, "errorCode": "internal-error", "requestId": "0c9f2ea5-fe09-463c-8f96-b6cae67800ad" } ] } ``` Ignore members you do not know, inside the entry and inside `more`: new codes and new detail members can be added within v1. A resource of another partner is always answered as if it did not exist (`404`), never with `403`. ## Status codes | Status | Meaning | Typical codes | | - | - | - | | `400` | The request is malformed or invalid | [`data-failed-validation`](/errors/catalog#data-failed-validation), [`malformed-json`](/errors/catalog#malformed-json), [`invalid-header`](/errors/catalog#invalid-header), [`invalid-idempotency-key`](/errors/catalog#invalid-idempotency-key) | | `401` | No valid credential | [`unauthenticated`](/errors/catalog#unauthenticated), [`api-key-invalid`](/errors/catalog#api-key-invalid) | | `403` | An access gate refused the request | [`missing-permission`](/errors/catalog#missing-permission), [`api-access-inactive`](/errors/catalog#api-access-inactive), [`ip-not-allowed`](/errors/catalog#ip-not-allowed), [`partner-suspended`](/errors/catalog#partner-suspended), [`partner-role-missing`](/errors/catalog#partner-role-missing) | | `404` | Unknown resource, a resource of another partner, or an unknown path | `-not-found` such as [`voucher-set-not-found`](/errors/catalog#voucher-set-not-found); [`route-not-found`](/errors/catalog#route-not-found) | | `405` | The path exists, the method does not; see the `Allow` header | [`method-not-allowed`](/errors/catalog#method-not-allowed) | | `409` | Conflict with the current state | [`voucher-set-depleted`](/errors/catalog#voucher-set-depleted), [`price-acceptance-required`](/errors/catalog#price-acceptance-required), [`idempotency-key-in-use`](/errors/catalog#idempotency-key-in-use), [`concurrent-update`](/errors/catalog#concurrent-update) | | `410` | Gone: expired or past its sunset | [`api-version-sunset`](/errors/catalog#api-version-sunset), [`webhook-event-expired`](/errors/catalog#webhook-event-expired) | | `422` | Well-formed, but a business rule refuses it | [`idempotency-key-reused`](/errors/catalog#idempotency-key-reused), [`batch-too-large`](/errors/catalog#batch-too-large), [`void-window-expired`](/errors/catalog#void-window-expired) | | `429` | Too many requests from your IP address: the network edge allows up to 30,000 authenticated requests per 60 seconds (1,200 for the keyless `/public/` and `/auth/` endpoints) and then blocks the address for 60 seconds; wait for `Retry-After` ([Edge limits](/guides/limits#edge-limits)) | none: the edge answers, so the body is not this error format | | `500` | Unexpected failure | [`internal-error`](/errors/catalog#internal-error) | | `502` | A service the platform depends on failed | [`upstream-error`](/errors/catalog#upstream-error), [`upstream-unavailable`](/errors/catalog#upstream-unavailable) | | `503` | Temporarily overloaded or unavailable; see `Retry-After` | [`service-unavailable`](/errors/catalog#service-unavailable), [`db-unavailable`](/errors/catalog#db-unavailable), [`query-timeout`](/errors/catalog#query-timeout) | | `504` | A dependency did not answer in time | [`upstream-timeout`](/errors/catalog#upstream-timeout) | `401` responses carry a `WWW-Authenticate` header. Each operation's reference page lists the codes it can return in addition to the common ones. ## Validation errors A body, query parameter, path parameter or header that does not pass validation is answered with `400` [`data-failed-validation`](/errors/catalog#data-failed-validation). All problems are collected in one pass and listed in `more.validationErrors`: | Member | Meaning | | - | - | | `property` | Where the problem is: a path into your body (`voucherSetId`, `redemptionEntity.refId`, `items[2].quantity`) or the name of the query parameter, path parameter or header (`perPage`, `redemptionId`, `Idempotency-Key`) | | `constraint` | The rule that failed, as a kebab-case item code: `required`, `unknown-field`, `unknown-parameter`, `invalid-id`, `invalid-timestamp`, `too-long`, `mutually-exclusive`, … The codes are listed under [Validation item codes](/errors/catalog#validation-item-codes) | | `message` | The explanation in English (`en`) and German (`de`) | | `params` | Parameters of the rule, when it has some: `min` and `max` for `invalid-page`, `allowed` for `invalid-sort` | | `value` | The rejected value, when it is safe to return | Typical causes: * a required property is missing (`required`) or `null` where it must not be (`null-not-allowed`); * a property or query parameter the operation does not define (`unknown-field`, `unknown-parameter`): the API is strict, so typos never pass silently; * an id that is not a positive integer (`invalid-id`); * a timestamp without an offset (`invalid-timestamp`); * a list parameter outside its rules: `page` or `perPage` out of range (`invalid-page`), a `sort` field that is not allowed (`invalid-sort`), or an unsupported filter field, operator or value (`invalid-filter`); see [Pagination](/guides/pagination#invalid-list-parameters); * both `voucherSetId` and `providerId` in a redemption (`mutually-exclusive`). Batch operations and content checks use the same list for item failures: `property` names the item (`items[2]`) and `constraint` the reason. ## Retrying safely Combine retries with [idempotency keys](/guides/idempotency): a key makes a `POST` safe to send again. | Response | Retry? | How | | - | - | - | | Network error or timeout | yes | Same `Idempotency-Key`, exponential backoff with jitter | | `409 idempotency-key-in-use` | yes | Same key, after `Retry-After` (1 second) | | `409 concurrent-update` | yes | Same key, with backoff: these outcomes are not stored | | `429` from the network edge | yes | Same key, after `Retry-After`; slow down (one request at a time for large downloads) | | `500`, `502`, `503`, `504` | yes | Same key, after `Retry-After` when present, else exponential backoff | | Any other `4xx` | no | Change the request first; then use a **new** key, because a stored `4xx` is final for its key | Never retry `401` or `403` in a loop: they need a configuration change (a valid key, a permission, an allowlist entry or an active partner). ## Getting help If you cannot resolve an error: 1. Look up the `errorCode` in the [error catalog](/errors/catalog) and follow "What to do". 2. Check the operation's reference page for the codes and rules it documents. 3. Contact your AllRide account manager, or AllRide through the **Support** link at the top of this site, with the `X-Request-Id` response header (for `5xx` also in `requestId`), the time of the request, the operation and the `errorCode`. Do not send API keys, webhook secrets or issued voucher codes. ## Related * [Error catalog](/errors/catalog) * [Idempotency](/guides/idempotency) * [Requests and responses](/guides/requests-and-responses) * [Authentication](/guides/authentication) # API keys Source: https://allride-voucher-consumer.mintlify.app/guides/api-keys Create API keys with the right scopes, recognise their format, rotate them without downtime and revoke them. API keys authenticate your backend systems. Each key belongs to one partner and one environment and carries a fixed set of permissions, its **scopes**. > [!WARNING] > > Use the API playground on these docs only with sandbox `vk_test_` keys. Playground requests are sent from Mintlify's servers, so they fail when your sandbox IP allowlist is enforced. ## Create a key Keys are created only by signed-in users in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/api-keys) (or with [`POST /partner/v1/api-keys`](/api-reference/partner-api-keys-create) and a dashboard session). An API key can never create, roll or revoke keys. You need: * the permission `manage-api-keys` (the owners, administrators and developers groups have it) and a sign-in within the last 15 minutes; * API access in status `active` ([Authentication](/guides/authentication#api-access-activation)); * fewer than 20 active keys in your partner (else [`api-key-limit-reached`](/errors/catalog#api-key-limit-reached)). A key has a `name` (1-80 characters), its `scopes` and an optional `expiresAt` of at most 2 years ahead. The dashboard suggests one year. **The secret is shown once**, in the response to the create call. Copy it into your secret manager straight away. Afterwards the API and the dashboard show only the key's metadata: `name`, `keyHint`, `last4`, `scopes`, `status`, `expiresAt`, `lastUsedAt` and `lastUsedIp`. If you lose the secret, create a new key. If the create response is lost (for example a timeout) and you retry with the same `Idempotency-Key`, the replayed response has `"secret": null` and the header `Idempotent-Replay-Redacted: true`. The key was created, but its secret cannot be shown again: revoke that key and create a new one. Owners of your partner and your technical contact receive an email whenever a key is created or rolled, and before a key expires. ## Key format ```text theme={null} vk___ ``` * `environment` is `live` (production) or `test` (sandbox). * `key id` is 26 lower-case Crockford base32 characters that identify the key. * `secret` is 43 base62 characters. The complete pattern is: ```text theme={null} ^vk_(live|test)_[0-9a-hjkmnp-tv-z]{26}_[0-9A-Za-z]{43}$ ``` Add it to your secret scanners (for example as a custom pattern in your repository's secret scanning) so that a committed key is caught. ## Scopes Scopes are the permissions the key may use. They are fixed when you create the key; to change them, create a new key. * Choose only **delegable** permissions. The permissions marked "no" in the [permission reference](/guides/permissions#permission-reference) (for example `manage-api-keys`, `manage-consumer-billing` and `accept-consumer-pricing`) can never be scopes: a create request with one is refused with `422` [`scope-not-delegable`](/errors/catalog#scope-not-delegable). * You can only grant permissions you hold yourself; otherwise the request is refused with `403` [`missing-permission`](/errors/catalog#missing-permission). * A request made with the key uses only the key's scopes, never the permissions of the user who created it. Typical consumer integrations use `view-consumer-catalog`, `create-consumer-redemptions`, `view-consumer-redemptions`, `manage-consumer-redemption-entities` and `erase-consumer-redemption-entities`; an ERP integration for invoices uses `view-consumer-billing`. See [Choosing scopes](/guides/permissions#choosing-scopes). ## Rotate a key Rotate keys regularly (at least every 12 months) and whenever someone who had access to a secret leaves. **Roll (recommended).** [`POST /partner/v1/api-keys/{apiKeyId}:roll`](/api-reference/partner-api-keys-roll) with a `gracePeriodMinutes` between 1 and 10,080 (default 1,440, that is 24 hours) creates a **new** key named ` (rolled)` with the same scopes and returns its secret once (`201`). Both keys work during the grace period; afterwards the old key gets `401` [`api-key-invalid`](/errors/catalog#api-key-invalid). ```json theme={null} { "gracePeriodMinutes": 1440 } ``` 1. Roll the key in the dashboard and store the new secret. 2. Deploy the new secret to your systems within the grace period. 3. Check `lastUsedAt` of the new key (and that the old key's `lastUsedAt` stops moving). A rolled key keeps the idempotency scope of the key it replaces: a retry that you started with the old key and repeat with the new key and the same `Idempotency-Key` replays the stored response instead of redeeming twice ([Idempotency](/guides/idempotency)). **Manual rotation (no grace period).** Create a new key, deploy it, verify its `lastUsedAt`, then revoke the old key. A newly created key does **not** share the old key's idempotency scope, so do not switch keys in the middle of a retry: finish or reconcile open requests first. ## Revoke a key Revoke a key in the dashboard or with [`POST /partner/v1/api-keys/{apiKeyId}:revoke`](/api-reference/partner-api-keys-revoke) (optional `reason`, up to 500 characters, kept in your audit log). Revocation takes effect on every server within 60 seconds; the key then gets `401` `api-key-invalid`. A revoked key cannot be reactivated. Revoke a key immediately when you suspect it leaked, and tell AllRide. AllRide can also revoke a key in an emergency and informs your partner owners when it does. ## Store keys safely * Keep keys in a secret manager and inject them at runtime. Never commit them, embed them in mobile or browser apps, or put them in URLs. * Do not log the `Authorization` header. If you need to identify a key in logs, log its `keyHint` or `last4`. * Use one key per system and per environment, so that you can rotate or revoke one without affecting the others. * Set an expiry and track it; you receive reminder emails 14 days and 1 day before a key expires. * Restrict where keys can be used from with the [IP allowlist](/guides/ip-allowlist). ## Related * [Authentication](/guides/authentication) * [Permissions and scopes](/guides/permissions) * [Idempotency](/guides/idempotency) # Authentication Source: https://allride-voucher-consumer.mintlify.app/guides/authentication Authenticate every request with an API key, understand which partner it acts for, and handle 401 and 403 errors. ## Send your API key Every request to `/consumer/v1` and `/partner/v1` carries your API key as a bearer credential: ```bash theme={null} curl -s "$VOUCHER_API_BASE_URL/partner/v1/principal" \ -H "Authorization: Bearer $VOUCHER_API_KEY" ``` * Send the key **only** in the `Authorization: Bearer` header. Keys in query strings, request bodies or other headers are ignored, so such a request counts as unauthenticated. * The API is HTTPS only, with TLS 1.2 or newer. There is no plain-HTTP listener and no redirect from HTTP. * Sandbox keys start with `vk_test_` and work only against `https://api.voucher-staging.allride.io`; production keys start with `vk_live_` and work only against `https://api.voucher.allride.io`. [`GET /partner/v1/principal`](/api-reference/partner-principal-get) tells you which partner, environment and permissions a key has. Use it as a health check when you deploy a new key. ## Partner context An API key belongs to exactly one partner, and every request made with it acts for that partner. There is no header to choose a partner: a request can never read or change another partner's data, and an id that belongs to another partner answers `404` as if it did not exist. `/consumer/v1` requires a consumer partner. A key of a partner that is only a provider gets `403` [`partner-role-missing`](/errors/catalog#partner-role-missing). `/partner/v1` is shared by providers and consumers; a few of its operations (SMTP configuration, email templates and custom domains) are consumer-only. The consumer dashboard calls the same API, but with the signed-in user's Firebase ID token instead of an API key. Some operations that change security or billing settings, or accept prices, are reserved for those dashboard users; the API reference marks them "Dashboard session only. API keys cannot call this operation." ## API access activation API keys work only while your partner's **API access** is `active`. AllRide activates it: 1. A user with the `request-api-access` permission requests it in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/api-access) ([`POST /partner/v1/api-access:request`](/api-reference/partner-api-access-request)). 2. AllRide reviews the request and activates API access, or declines it with a reason. You get an email either way. 3. Once `active`, members with `manage-api-keys` can create keys. [`GET /partner/v1/api-access`](/api-reference/partner-api-access-get) shows the status: `not-requested`, `requested`, `active` or `suspended`. AllRide can suspend API access; while it is not `active`, every API-key request is refused with `403` [`api-access-inactive`](/errors/catalog#api-access-inactive). Your partner itself must also be `active`. ## When authentication fails The API checks the credential first, then a fixed sequence of gates. The first failing check answers. | Status | `errorCode` | Meaning | What to do | | - | - | - | - | | 401 | [`unauthenticated`](/errors/catalog#unauthenticated) | No credential was sent | Send `Authorization: Bearer ` | | 401 | [`token-invalid`](/errors/catalog#token-invalid) | The header is malformed or uses an unknown scheme | Check the header format | | 401 | [`api-key-invalid`](/errors/catalog#api-key-invalid) | The key is unknown, revoked, expired, past its roll grace period or from the other environment | Check the key and the base URL; create or roll a key | | 403 | [`partner-offboarded`](/errors/catalog#partner-offboarded) | Your partner was offboarded | Contact AllRide | | 403 | [`partner-suspended`](/errors/catalog#partner-suspended) | Your partner is suspended and the request is a write | Contact AllRide | | 403 | [`api-access-inactive`](/errors/catalog#api-access-inactive) | API access is not `active`, or the partner is not `active` | Request API access or contact AllRide | | 403 | [`ip-not-allowed`](/errors/catalog#ip-not-allowed) | The source address is not on your IP allowlist | See [IP allowlist](/guides/ip-allowlist) | | 403 | [`missing-permission`](/errors/catalog#missing-permission) | The key's scopes lack a permission of the operation; `more.requiredPermissions` lists them | See [Permissions and scopes](/guides/permissions) | | 403 | [`partner-role-missing`](/errors/catalog#partner-role-missing) | Your partner lacks the consumer role this surface needs | Use the surface of your partner's role | An unknown, revoked or expired key always gets the same `api-key-invalid` answer, so the response never reveals whether a key exists. A revoked key stops working within 60 seconds. ```json theme={null} { "errors": [ { "message": "The API key is unknown, revoked, expired, past its roll grace period or belongs to the other environment.", "statusCode": 401, "errorCode": "api-key-invalid" } ] } ``` Every `401` also carries the header `WWW-Authenticate: Bearer realm="voucher"`. Do not retry authentication errors in a loop: fix the key or the configuration first. ## Security recommendations * Keep keys in a secret manager and inject them at runtime; never commit them, put them in client apps or browsers, or log them. Add the key pattern `^vk_(live|test)_[0-9a-hjkmnp-tv-z]{26}_[0-9A-Za-z]{43}$` to your secret scanners. * Use one key per system and environment, with the minimum scopes that system needs ([Permissions and scopes](/guides/permissions)). * Give keys an expiry and rotate them at least every 12 months with `:roll` ([API keys](/guides/api-keys)). * Turn on the [IP allowlist](/guides/ip-allowlist) for your API keys. * Call the API only from your backend. The API does not serve browsers other than the AllRide dashboards. * If a key leaks, revoke it immediately in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/api-keys) and tell AllRide. ## Related * [API keys](/guides/api-keys) * [Permissions and scopes](/guides/permissions) * [Errors](/errors) # Concepts Source: https://allride-voucher-consumer.mintlify.app/guides/concepts Partners, roles and the core resources of the AllRide Voucher Platform, and how a voucher set moves through your catalog. These are the names used everywhere in the API, the dashboards and these docs. Each concept has exactly one name. ## Partners and roles * **Partner**: a company that has a contract with AllRide. A partner is a provider, a consumer, or both, and is the owner of everything it creates. Every API key and every dashboard user acts for exactly one partner. * **Partner Provider** ("provider"): a partner that owns voucher sets and vouchers, for example a scooter or bike operator. * **Partner Consumer** ("consumer"): a partner that offers voucher sets to its own end customers and redeems them. You are a consumer. Consumers are the only partners AllRide bills. * **End Customer**: the person your app or landing page serves. End customers do not have accounts on the platform; you represent them as redemption entities if you want to. * **User** and **Partner Membership**: a person with a login, and the link that makes them a member of your partner. One person can be a member of several partners. * **Group** and **Permission**: permissions are capabilities with kebab-case names such as `create-consumer-redemptions`; groups carry permissions, and members get the union of their groups' permissions and their own direct permissions ([Members and groups](/workspace/members-and-groups)). * **API Access**: a partner-level switch that AllRide activates before API keys work. * **API Key**: a secret credential of your partner whose **scopes** are a subset of the delegable permissions ([API keys](/guides/api-keys)). ## Core resources **Provider content** * **Voucher Set**: a provider's collection of vouchers that share content, terms, kind, link format and schedule. * **Voucher Set Revision**: an approved, unchangeable snapshot of a voucher set's content. You only ever see the **live revision**; providers change content through a **Change Request** that AllRide approves before it reaches you. * **Voucher**: one redeemable item of a set, either a **code** or a **link**. The **Voucher Kind** (`code` or `link`) is declared per voucher set. * **Redemption Type**: `single` (a voucher can be redeemed once) or `multiple` (it can be redeemed up to **Max Redemptions** times; `null` means unlimited). * **Validity Window**: `validFrom` and `validUntil` of a voucher. * **Availability**: how a visible voucher set looks to you: `upcoming`, `active` or `deactivation-scheduled`. **Your side** * **Consumer Catalog**: the providers and voucher sets AllRide makes available to you, with their live content ([Catalog and toggles](/consumer/catalog)). * **Catalog Toggle**: your on/off switch per provider (**Provider Toggle**) and per voucher set (**Voucher Set Toggle**). * **Price Terms** and **Price Acceptance**: the price you pay per redemption of a voucher set, and a user's recorded agreement to it. A set is redeemable only with an accepted price ([Prices and price acceptance](/consumer/prices)). * **Redemption**: the assignment of one voucher use to you (and optionally to a redemption entity), recorded in the ledger. Its **Redemption Channel** says how it was created: `api`, `dashboard`, `redemption-link` or `import`. * **Void**: marking a redemption as cancelled for reporting and limits. A void never returns the voucher to stock ([Voiding redemptions](/consumer/voiding)). * **Redemption Entity**: your record of who or what redeems (a customer, a device, an employee), with an optional opaque `refId` and `metadata` ([Redemption entities](/consumer/redemption-entities)). * **Redemption Link**: a URL you create that lets an end customer redeem on an AllRide-hosted landing page ([Redemption links](/consumer/redemption-links)). * **Email OTP Gate**: a Redemption Link option that asks the visitor to confirm an email address with a one-time code before redeeming ([Email OTP links](/consumer/email-otp-links)). * **Landing Page**: the page, in your Workspace Branding, that serves a Redemption Link, on AllRide's host or your own **Custom Domain** ([Landing pages and custom domains](/consumer/landing-pages)). **Integration** * **Webhook Endpoint**: an HTTPS URL of yours that receives signed events; each attempt to send one is a **Webhook Delivery** ([Webhooks](/webhooks/overview)). * **Idempotency Key**: the optional `Idempotency-Key` header that makes a POST safe to retry ([Idempotency](/guides/idempotency)). * **Audit Event**: an append-only record of a change in your partner ([Audit log](/workspace/audit-log)). ## Lifecycle at a glance Providers create and schedule voucher sets; AllRide approves their content and decides which consumers may see them. From your side, a voucher set appears in your catalog, becomes redeemable, and eventually leaves it: ```mermaid theme={null} stateDiagram-v2 direction LR [*] --> upcoming: available to you (scheduled) [*] --> active: available to you (already active) upcoming --> active: activation time reached active --> deactivation_scheduled: provider schedules the end deactivation_scheduled --> active: schedule cancelled deactivation_scheduled --> [*]: deactivated active --> [*]: deactivated, suspended or access removed upcoming --> [*]: activation cancelled or access removed ``` * A set enters your catalog with the webhook [`catalog-voucher-set-available`](/webhooks/events/catalog-voucher-set-available) and leaves it with [`catalog-voucher-set-deactivated`](/webhooks/events/catalog-voucher-set-deactivated) or [`catalog-voucher-set-unavailable`](/webhooks/events/catalog-voucher-set-unavailable). * Content changes arrive as a new live revision with [`catalog-voucher-set-updated`](/webhooks/events/catalog-voucher-set-updated). * You can redeem a set when its `availability` is `active` or `deactivation-scheduled`, your Provider Toggle and Voucher Set Toggle are on, its Price Terms are accepted and it has stock. * Every redemption sends [`redemption-created`](/webhooks/events/redemption-created); a void sends [`redemption-voided`](/webhooks/events/redemption-voided). ## Related * [Catalog and toggles](/consumer/catalog) * [Redeeming vouchers](/consumer/redeeming) * [Permissions and scopes](/guides/permissions) # Sandbox and production Source: https://allride-voucher-consumer.mintlify.app/guides/environments The sandbox and production environments: base URLs, dashboards, API key prefixes, test data and how to move to production. ## Environments at a glance AllRide runs two environments for partners. The **sandbox** is where you build and test; **production** is where real vouchers are redeemed. | | Sandbox | Production | | - | - | - | | API base URL | `https://api.voucher-staging.allride.io` | `https://api.voucher.allride.io` | | Developer dashboard | [consumer-developers.voucher-staging.allride.io](https://consumer-developers.voucher-staging.allride.io/go/overview) | [consumer-developers.voucher.allride.io](https://consumer-developers.voucher.allride.io/go/overview) | | API key prefix | `vk_test_` | `vk_live_` | | `environment` in [`GET /partner/v1/principal`](/api-reference/partner-principal-get) | `test` | `live` | | Data | Test data; never real voucher codes | Real vouchers, redemptions and invoices | | Egress addresses | [Network and IP addresses](/guides/network) | [Network and IP addresses](/guides/network) | The sandbox is a **separate environment**, not a mode of production. It has its own partner accounts, users, API keys, webhook endpoints, settings and data. Nothing you create in one environment exists in the other, and there is no "test mode" switch in production. Keys work only in their own environment. A `vk_test_` key sent to production, or a `vk_live_` key sent to the sandbox, is refused with `401` and [`api-key-invalid`](/errors/catalog#api-key-invalid). > [!WARNING] > > Use the API playground on these docs only with sandbox `vk_test_` keys. Playground requests are sent from Mintlify's servers, so they fail when your sandbox IP allowlist is enforced. ## Getting sandbox access 1. Send your AllRide account manager (or AllRide through the **Support** link at the top of this site) your company name and the email address of your first user. AllRide creates your consumer partner in the sandbox and invites that user. 2. Sign in to the [sandbox developer dashboard](https://consumer-developers.voucher-staging.allride.io/go/api-access) and request API access. Requesting needs the `request-api-access` permission, which owners and the developers group hold. 3. AllRide reviews and activates API access. API keys can be created and used only while API access is `active`; [`GET /partner/v1/api-access`](/api-reference/partner-api-access-get) shows the current status. 4. Create an API key in [API keys](https://consumer-developers.voucher-staging.allride.io/go/api-keys) ([API keys](/guides/api-keys)). ## Test data The sandbox contains fixtures so that you can redeem from your first minute: * The provider **`sandbox-scooters`** with the voucher sets **Sandbox ride credit** (a code voucher that can be redeemed many times), **Sandbox link voucher** (a link voucher), **Sandbox depleted** (no voucher left, so redeeming it returns `409` [`voucher-set-depleted`](/errors/catalog#voucher-set-depleted)) and **Sandbox upcoming** (activates in a year, so its `availability` is `upcoming`). * When your consumer partner is activated, the sandbox switches the provider `sandbox-scooters` on for you, so its sets appear in your catalog with `filter[providerToggle]=true`. * **A €0 sandbox price.** The sandbox has a €0 provider price for `sandbox-scooters` that is never billed. Each new consumer partner sees its sets as `priceAcceptance.status: acceptance-required` and accepts that price **once** in **Prices** in the sandbox consumer dashboard; that is your first step before redeeming. Without it, a redemption answers `409` [`price-acceptance-required`](/errors/catalog#price-acceptance-required). * Sandbox invoices carry numbers starting with `TEST-`. You can create your own redemption entities, Redemption Links and webhook endpoints freely. Use only fictional customer data: never put personal data of real people into the sandbox. ## Differences from production * **Data:** the sandbox never contains real voucher codes. Codes you redeem there are worthless outside the sandbox. * **Availability:** the sandbox has no service level objective and may be unavailable during maintenance. AllRide may reset sandbox data; a reset is announced by email to your technical contact (`contacts.technicalEmail`) at least 14 days ahead. * **Payments:** billing runs in the payment provider's test mode; no money moves. * **Prices:** production has no seeded prices. Each provider's sets are priced by AllRide, and you accept their Price Terms before you can redeem (step 4 below). * **Same API:** paths, request and response formats, permissions, errors and webhooks are identical in both environments. Code that works against the sandbox works against production once you change the base URL and the key. ## Moving to production 1. Ask your AllRide account manager for your production partner account (if you do not have one yet) and request API access in the [production developer dashboard](https://consumer-developers.voucher.allride.io/go/api-access). 2. Create a production `vk_live_` key with the minimum scopes your integration needs and store it in a secret manager. 3. Recreate your configuration in production: webhook endpoints (with new secrets), IP allowlist entries, SMTP configuration and email templates, custom domains and catalog settings. None of these are copied from the sandbox. 4. Accept the Price Terms of the voucher sets you want to offer in the production consumer dashboard. 5. Switch `VOUCHER_API_BASE_URL` to `https://api.voucher.allride.io`. 6. Work through the [going-live checklist](/guides/going-live). ## Related * [Quickstart](/quickstart) * [Network and IP addresses](/guides/network) * [Going-live checklist](/guides/going-live) # Going-live checklist Source: https://allride-voucher-consumer.mintlify.app/guides/going-live Everything to check before your integration redeems real vouchers in production. Work through this list before you switch your integration from the sandbox to production. Each item links to the guide that explains it. ## Checklist **Account and access** * [ ] Your production consumer partner exists and its API access is `active` ([`GET /partner/v1/api-access`](/api-reference/partner-api-access-get) shows `"status": "active"`). * [ ] A production `vk_live_` key exists for each system that calls the API, with the minimum scopes it needs, stored in a secret manager and never in code or logs ([API keys](/guides/api-keys), [Permissions and scopes](/guides/permissions)). * [ ] You have a plan to rotate keys with `:roll` before they expire. * [ ] Your technical contact (`contacts.technicalEmail` on the partner record) is current: key expiry warnings and deprecation notices go there. **Network security** * [ ] Your outbound IPv4 and IPv6 ranges are on the IP allowlist and enforcement is on ([IP allowlist](/guides/ip-allowlist)). * [ ] Your client calls the API only over `https://` with TLS 1.2 or newer and validates the server certificate against your system trust store, without pinning it ([Network and IP addresses](/guides/network)). * [ ] Your outbound firewall (if any) allows the API by hostname, not by IP address: the API hosts are served through Cloudflare and have no static inbound addresses ([Network and IP addresses](/guides/network)). * [ ] All four of AllRide's egress addresses, including the two disaster-recovery addresses, are allowed by the firewall in front of your webhook receiver (and your SMTP server, if you use email OTP links) ([Network and IP addresses](/guides/network)). **Webhooks** * [ ] A production webhook endpoint is registered for the event types you handle ([Webhooks](/webhooks/overview)). * [ ] Your receiver verifies `x-signing-signature` against either of your two secrets, over the raw body, and rejects old timestamps ([Verifying signatures](/webhooks/verify-signatures)). * [ ] Your handlers are idempotent: deliveries can repeat and arrive out of order. * [ ] Your receiver answers `2xx` within 15 seconds and does the work afterwards. **Requests** * [ ] Every POST sends an `Idempotency-Key`, and retries reuse the key of the same intent ([Idempotency](/guides/idempotency)). * [ ] Your retry policy follows the [errors guide](/errors): retry 5xx, network errors and the retryable conflicts with the same key, and an edge `429` after `Retry-After`; never retry other 4xx without changing the request. * [ ] Your client ignores unknown fields and treats enums as open sets ([Versioning and deprecation](/guides/versioning)). * [ ] Your JSON parser handles ids and counters above 2^53 ([Requests and responses](/guides/requests-and-responses)). * [ ] You log the `X-Request-Id` of failed calls so that support can trace them. **Business setup** * [ ] The Price Terms of the voucher sets you offer are accepted in the production consumer dashboard ([Prices and price acceptance](/consumer/prices)). * [ ] Your Provider Toggles and Voucher Set Toggles are set ([Catalog and toggles](/consumer/catalog)). * [ ] Your billing account, billing contacts and payment method are complete ([Billing](/consumer/billing)). * [ ] If you use Redemption Links: Workspace Branding with `landing.privacyUrl` and `landing.imprintUrl` set (required before you can create links; `landing.termsUrl` is optional, see [Landing pages](/consumer/landing-pages)), an active custom domain if you want one, and for email OTP links a verified SMTP configuration with an active template. **Staying informed** * [ ] `contacts.technicalEmail` on your partner is a monitored mailbox (deprecation, network and sandbox notices go there). ## Related * [Sandbox and production](/guides/environments) * [Authentication](/guides/authentication) * [Errors](/errors) # Idempotency Source: https://allride-voucher-consumer.mintlify.app/guides/idempotency Retry POST requests safely with the Idempotency-Key header: how replays work, key scope across key rolls, and a retry strategy. Networks fail. A timeout after you sent `POST /consumer/v1/redemptions` does not tell you whether the voucher was redeemed. The `Idempotency-Key` header makes such a retry safe: the platform runs the request once and answers every retry with the stored response. ## When to use idempotency keys * **Every `POST` of every surface** accepts the optional `Idempotency-Key` header: creates, commands such as `:void` and `:revoke`, and batches. * Send one on every `POST` that has an effect you must not repeat. For redemptions it is essential: without a key, a retry after a timeout can redeem a second voucher. * Without a key the request is processed normally, every time. * `GET`, `PUT`, `PATCH` and `DELETE` ignore the header; the response then carries `Idempotency-Key-Ignored: true`. `PUT`, `PATCH` and `DELETE` are naturally repeatable; see [Concurrent changes](/guides/requests-and-responses#concurrent-changes). ## Choosing a key * A key is 1 to 255 printable ASCII characters (`!` to `~`), without spaces. An empty key, a longer one, or one with a space or a non-ASCII character is answered with `400` [`invalid-idempotency-key`](/errors/catalog#invalid-idempotency-key). * Use a UUID (v4 or v7) or a deterministic business key such as `order-8812:line-1`. * Use **one key per intent** (one customer action, one order line) and reuse it for every retry of that intent. Never reuse a key for a different request. * Never put personal data into a key: keys are stored and can be read back with the redemption. ```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:48213"}}' ``` ## How replays work The platform fingerprints each request: method, route, path parameters, query parameters and the canonical JSON body. Key order and whitespace in the body do not matter; numeric literals do (`1.0` and `1` are different bodies), so resend identical values. | You send | You get | | - | - | | A new key | Normal processing; the outcome is stored for 24 hours | | The same key and the same request | The stored response, with `Idempotent-Replayed: true`. Nothing runs again: no second redemption, no audit entry, no webhook | | The same key and a different request | `422` [`idempotency-key-reused`](/errors/catalog#idempotency-key-reused) | | The same key while the first request is still running | `409` [`idempotency-key-in-use`](/errors/catalog#idempotency-key-in-use) with `Retry-After: 1` | **Stored outcomes.** Successful responses (`2xx`) and final client errors are stored and replayed: `400` (`malformed-json`, `invalid-header`, `data-failed-validation`), `404`, domain `409` conflicts, `410` and `422`. A stored domain `409` such as [`voucher-set-depleted`](/errors/catalog#voucher-set-depleted) is **final for that key**: a retry with the same key replays the `409` even after new stock arrived. A new attempt needs a **new key**. **Not stored** (a retry with the same key is safe and runs again): `409` [`concurrent-update`](/errors/catalog#concurrent-update), `409 idempotency-key-in-use`, `422 idempotency-key-reused`, every `5xx`, timeouts and network failures. `401` and `403` are decided before the key is claimed and are never stored. **Show-once values.** Responses that contain a secret shown only once (API key create and roll, webhook endpoint create and secret rotation, the SEPA setup `clientSecret`) are stored with that secret set to `null`. A replay returns `null` there and carries `Idempotent-Replay-Redacted: true`. If you lost the original response, revoke the key or rotate the secret and create a new one. **Issued vouchers replay in full.** A redemption's `issued.code` and `issued.link` are not show-once: a replay of `POST /consumer/v1/redemptions` or `:batch` returns the identical body, issued values included. **Asynchronous jobs.** A replayed `202` shows the job as it was when it was accepted. Follow `Location` for its current state. Keys are kept for **24 hours**. After that the same key executes again, which is another reason never to reuse keys across intents. ## Key scope and key rotation Keys are scoped to the caller: * For API keys, the scope is the API key's **roll chain**. A key created with [`:roll`](/api-reference/partner-api-keys-roll) shares the scope of the key it replaces, so retrying an intent through the rolled key with the same `Idempotency-Key` **replays**: there is no second effect. * An **independently created** API key has its own scope. Retrying an intent through it with the same `Idempotency-Key` is **not** a replay and executes again; for a redemption that means a second voucher. * A dashboard user's keys are scoped to that user and partner. Rule for integrations: rotate API keys with `:roll`, never by creating a fresh key and switching over in the middle of an intent. If you did switch to an independent key, or lost track of an intent, reconcile before you retry (see below). ## Errors | Status and code | Meaning | What to do | | - | - | - | | `400` [`invalid-idempotency-key`](/errors/catalog#invalid-idempotency-key) | The key is malformed | Send 1-255 printable ASCII characters, no spaces | | `409` [`idempotency-key-in-use`](/errors/catalog#idempotency-key-in-use) | The first request with this key is still running | Wait `Retry-After` seconds, retry with the **same** key | | `422` [`idempotency-key-reused`](/errors/catalog#idempotency-key-reused) | The key was used for a different request | Fix your key generation; use a new key for a new request | ## Retry strategy Retry with the **same** key on network errors, timeouts, `409 idempotency-key-in-use`, `409 concurrent-update` and `5xx`, with exponential backoff and jitter, honouring `Retry-After`. Do not retry other `4xx` responses without changing the request; when you change it, use a new key. ```ts theme={null} async function postIdempotent(path: string, body: unknown, key = crypto.randomUUID()): Promise { for (let attempt = 0; attempt < 5; attempt++) { const res = await fetch(`${process.env.VOUCHER_API_BASE_URL}${path}`, { method: "POST", headers: { Authorization: `Bearer ${process.env.VOUCHER_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": key }, body: JSON.stringify(body), }).catch(() => undefined); // network error: retry with the same key if (res && res.status < 500 && res.status !== 409 && res.status !== 429) return res; if (res?.status === 409) { // only transient 409s are retryable with the same key const { errors } = await res.clone().json(); if (!["idempotency-key-in-use", "concurrent-update"].includes(errors?.[0]?.errorCode)) return res; } const retryAfter = Number(res?.headers.get("Retry-After") ?? 0); await new Promise(r => setTimeout(r, Math.max(retryAfter * 1000, 2 ** attempt * 250 + Math.random() * 250))); } throw new Error("Gave up after 5 attempts; the same Idempotency-Key is still safe to retry later (24 h)"); } ``` Persist the key together with the intent (for example on your order line) before the first attempt, so a retry after a crash of your own service uses the same key. ## Where to use it in the Consumer API Send a key on every: * [`POST /consumer/v1/redemptions`](/api-reference/consumer-redemptions-create) and [`POST /consumer/v1/redemptions:batch`](/api-reference/consumer-redemptions-batch) (one key covers the whole batch); * [`POST /consumer/v1/redemptions/{redemptionId}:void`](/api-reference/consumer-redemptions-void); * [`POST /consumer/v1/redemption-entities`](/api-reference/consumer-redemption-entities-create) and the entity commands; * [`POST /consumer/v1/redemption-links`](/api-reference/consumer-redemption-links-create) and [`POST /consumer/v1/redemption-links:batch`](/api-reference/consumer-redemption-links-batch); * every workspace `POST`, for example [create a webhook endpoint](/api-reference/partner-webhook-endpoints-create). **Reconciling a redemption intent.** Every redemption stores the `Idempotency-Key` it was created with (a batch stores its key on each of its redemptions). If you are unsure whether an intent ran, for example after moving to an independently created API key, look it up: ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/consumer/v1/redemptions" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "filter[idempotencyKey]=order-8812:line-1" ``` * A **non-empty page** means the intent already ran: use those redemptions and do not redeem again. * An **empty page** means it did not run: retry with the same key. A `409 price-acceptance-required` or `409 voucher-set-depleted` is stored for its key. After you accepted the price or new stock arrived, redeem again with a **new** key. ## Related * [Redeeming vouchers](/consumer/redeeming) * [Errors](/errors) * [API keys](/guides/api-keys) * [Requests and responses](/guides/requests-and-responses) # IP allowlist Source: https://allride-voucher-consumer.mintlify.app/guides/ip-allowlist Allow API calls only from your own network ranges, switch enforcement on safely, and troubleshoot ip-not-allowed errors. The IP allowlist restricts which source addresses may call the API for your partner. It protects you if an API key leaks: a stolen key is useless outside your networks. > [!WARNING] > > Use the API playground on these docs only with sandbox `vk_test_` keys. Playground requests are sent from Mintlify's servers, so they fail when your sandbox IP allowlist is enforced. ## How the allowlist works * Your partner has up to **100** CIDR entries, each with a label. * The setting `ipAllowlistEnforcement` decides what is checked: | Value | Effect | | - | - | | `off` (default) | Nothing is checked, even if entries exist | | `api-keys` | Every API-key request must come from an address inside an entry | | `api-keys-and-users` | API-key requests **and** dashboard users' requests must come from an address inside an entry | * The address checked is your client's public source address, which is your **outbound NAT or proxy address**, not a private address inside your network. The API hosts are served through Cloudflare, and AllRide takes the address from the connection Cloudflare received from you (Cloudflare's `CF-Connecting-IP`), not from Cloudflare's own addresses. Headers such as `X-Forwarded-For`, `X-Client-IP` or `CF-Connecting-IP` that you send yourself are ignored or overwritten. * A request from outside the allowlist is refused with `403` [`ip-not-allowed`](/errors/catalog#ip-not-allowed). The response does not echo the address. The denial is recorded in your [audit log](/workspace/audit-log) as `security.ip-denied` (at most once per key or member per minute). * Changes take effect on every server within 60 seconds. ## Add your IP ranges Add entries in [IP allowlist](https://consumer-developers.voucher.allride.io/go/ip-allowlist) in the developer dashboard, or with [`POST /partner/v1/ip-allowlist`](/api-reference/partner-ip-allowlist-create) from a dashboard session. Changing the allowlist needs `manage-ip-allowlist` and a recent sign-in; it is not available to API keys, so a leaked key cannot widen its own access. Reading it ([`GET /partner/v1/ip-allowlist`](/api-reference/partner-ip-allowlist-list)) needs `view-ip-allowlist` and works with API keys. ```json theme={null} { "cidr": "203.0.113.0/24", "label": "Production NAT gateway" } ``` Rules for `cidr`: * canonical form with the host bits set to zero (`203.0.113.0/24`, not `203.0.113.7/24`); a single address is `/32` (IPv4) or `/128` (IPv6); * IPv4 prefixes of `/8` or longer, IPv6 prefixes of `/32` or longer; * no private, loopback or link-local ranges (such as the RFC 1918 private ranges or IPv6 link-local addresses) in production. An invalid entry fails with `400` [`data-failed-validation`](/errors/catalog#data-failed-validation) and the constraint [`invalid-cidr`](/errors/catalog#invalid-cidr). Adding a CIDR that already exists answers `409` [`ip-allowlist-entry-exists`](/errors/catalog#ip-allowlist-entry-exists); a 101st entry answers `422` [`ip-allowlist-limit-reached`](/errors/catalog#ip-allowlist-limit-reached). To change a range, delete the entry and add a new one; only the label can be edited. ## Enable enforcement 1. Add **all** the ranges your systems call from (IPv4 and IPv6, every region and every fallback NAT) **before** you enable enforcement. 2. Set `ipAllowlistEnforcement` to `api-keys` in your partner's security settings in the dashboard. This needs `manage-settings` and is not available to API keys. 3. Watch your integration and the audit log for `security.ip-denied` for a while. 4. Optionally switch to `api-keys-and-users` to restrict dashboard access too. Safety rules protect you from locking yourself out: * Enforcement cannot be switched on while the allowlist is empty, and the last entry cannot be deleted while enforcement is on: both answer `409` [`ip-allowlist-required`](/errors/catalog#ip-allowlist-required). Switch enforcement `off` first. * With `api-keys-and-users`, a change that would exclude your own current address is refused with `422` [`ip-allowlist-would-lock-out`](/errors/catalog#ip-allowlist-would-lock-out). ## IPv6 `api.voucher.allride.io` and `api.voucher-staging.allride.io` resolve to both IPv4 and IPv6 addresses. If your servers have IPv6 connectivity, they may connect over IPv6, and the address checked is then your IPv6 egress address. Either add your IPv6 egress range (for example `2001:db8:1200::/48`), or force your HTTP client to use IPv4. IPv4-mapped IPv6 addresses (`::ffff:203.0.113.7`) are matched against your IPv4 entries. ## Troubleshooting | Symptom | Likely cause | Fix | | - | - | - | | `403 ip-not-allowed` after a deployment | The new environment uses a different NAT address | Add the new egress range | | `403 ip-not-allowed` from some servers only | Some hosts connect over IPv6 | Add your IPv6 range or force IPv4 | | Calls fail from your laptop but work from servers | Expected: your office address is not on the list | Use a host inside an allowed range | | The docs playground fails | The playground calls from Mintlify's servers | Use the playground only with sandbox keys and enforcement off, or use `curl` | | `409 ip-allowlist-required` | Enforcement is on and you tried to delete the last entry, or switched it on with no entries | Add entries first, or switch enforcement off | To find the address AllRide sees, call any endpoint from the host in question with enforcement off, then check `lastUsedIp` of the key in the developer dashboard. ## Related * [Network and IP addresses](/guides/network) * [Authentication](/guides/authentication) # Limits Source: https://allride-voucher-consumer.mintlify.app/guides/limits Size and count limits of requests and resources, and how long data and logs are kept. The API enforces the size and count limits below. It has no per-partner rate tiers, no per-key quotas and sends no rate-limit headers. The only request-rate limits are the per-IP limits of the network edge ([Edge limits](#edge-limits)), and the only attempt limits are the ones that protect the [email OTP](/consumer/email-otp-links#limits) on Redemption Link landing pages. ## Request limits | Limit | Value | When exceeded | | - | - | - | | Items per list page (`perPage`) | 1 to 100, default 25 | `400 data-failed-validation`, constraint [`invalid-page`](/errors/catalog#invalid-page) | | List page number (`page`) | 1 to 100,000, default 1 | `400 data-failed-validation`, constraint [`invalid-page`](/errors/catalog#invalid-page) | | Sort fields per request | 1 | `400 data-failed-validation`, constraint [`invalid-sort`](/errors/catalog#invalid-sort) | | `filter[…]` parameters per request | 20 | `400 data-failed-validation`, constraint [`invalid-filter`](/errors/catalog#invalid-filter) | | Values per `in` filter | 100 | `400 data-failed-validation`, constraint [`invalid-filter`](/errors/catalog#invalid-filter) | | `search` term | 255 characters | `400 data-failed-validation` | | JSON nesting depth of a body | 32 levels | `400` [`malformed-json`](/errors/catalog#malformed-json) | | `Idempotency-Key` | 1 to 255 printable ASCII characters | `400` [`invalid-idempotency-key`](/errors/catalog#invalid-idempotency-key) | | Vouchers per batch redemption (sum of `quantity`) | 10 | `422` [`batch-too-large`](/errors/catalog#batch-too-large) | | Redemption Links per bulk create | 100 | `400 data-failed-validation` | | Webhook payload | 64 KiB | Payloads are kept below this size by design | | Webhook response time | 15 seconds | The delivery counts as failed and is retried | ## Edge limits The network edge in front of the API counts requests per source IP address. There are no per-partner or per-key quotas. | Traffic | Limit per source IP address | When exceeded | | - | - | - | | Authenticated API requests (every path except `/public/` and `/auth/`) | Up to 30,000 requests per 60 seconds (on average 500 per second) | `429` with `Retry-After`; the address is blocked for 60 seconds | | Keyless endpoints under `/public/` and `/auth/` | Up to 1,200 requests per 60 seconds | `429` with `Retry-After`; the address is blocked for 60 seconds | A short burst above 500 requests per second is fine as long as the 60-second total stays under 30,000. The edge answers the `429` itself, so its body is not the [error format](/errors): branch on the status and `Retry-After` only. If one egress IP address of yours needs more, contact your AllRide account manager or AllRide through the **Support** link at the top of this site. ## Resource limits | Limit | Value | | - | - | | `metadata` objects (redemptions, entities, links) | 16 KiB, nesting depth 5, 100 keys per object (constraint [`invalid-metadata`](/errors/catalog#invalid-metadata)) | | Redemption entity `refId` | 1 to 128 characters of letters, digits and `._:@/+=-` (constraint [`invalid-ref-id`](/errors/catalog#invalid-ref-id)) | | Catalog `externalRef` | 64 characters, unique per consumer | | Void window | 30 days after the redemption ([`void-window-expired`](/errors/catalog#void-window-expired)) | | IP allowlist entries | 100 per partner ([`ip-allowlist-limit-reached`](/errors/catalog#ip-allowlist-limit-reached)) | | Active API keys | 20 per partner ([`api-key-limit-reached`](/errors/catalog#api-key-limit-reached)) | | API key lifetime | at most 2 years | | Billing document download link | valid 15 minutes | ## Data retention | Data | Kept | | - | - | | Idempotency keys and stored responses | 24 hours | | Webhook delivery logs and replay | 30 days after the event (one entry per stored event and endpoint, with its attempts; replay works while the delivery is in the log) | | Audit log | 2 years online | | Redemptions | Kept as your ledger; [erasing a redemption entity](/consumer/redemption-entities#erasure-gdpr) removes its personal data and leaves the redemptions anonymous | ## Related * [Pagination, filtering and sorting](/guides/pagination) * [Idempotency](/guides/idempotency) * [Retries, logs and replay](/webhooks/retries-and-logs) # Network and IP addresses Source: https://allride-voucher-consumer.mintlify.app/guides/network The API hostnames, the addresses AllRide connects from, how address changes are announced, and the TLS requirements. ## API endpoint addresses | Environment | API host | | - | - | | Sandbox | `api.voucher-staging.allride.io` | | Production | `api.voucher.allride.io` | The API hosts are served through Cloudflare's anycast network. They have **no static inbound IP addresses**: the addresses they resolve to are Cloudflare's and can change at any time. If your network filters outbound traffic, allow the API hosts **by hostname** (for example in your proxy or firewall's FQDN rules). If your firewall can only work with addresses, allow Cloudflare's published ranges ([IPv4](https://www.cloudflare.com/ips-v4), [IPv6](https://www.cloudflare.com/ips-v6)). Both hosts resolve to IPv4 and IPv6. Do not derive addresses from a DNS lookup. The base URL and the addresses AllRide connects from are returned by the developer reference of each environment and shown on the [developer reference page](https://consumer-developers.voucher.allride.io/go/reference): ```bash theme={null} curl -sS "$VOUCHER_API_BASE_URL/partner/v1/developer-reference" \ -H "Authorization: Bearer $VOUCHER_API_KEY" | jq '.data | {apiBaseUrl, egressIps}' ``` `egressIps` lists the current addresses AllRide connects from ([Get developer reference information](/api-reference/partner-developer-reference-get)), including the two disaster-recovery addresses. It is a plain list without status or dates: it holds the addresses that are in use (`active` or `retiring`), and an `announced` address joins it only on its `since` date. Take announced addresses from the announcement email or the [egress address tables](#egress-address-tables). Do not derive the addresses from a DNS lookup. ## Addresses we connect from AllRide connects to your systems from **four** static egress IPv4 addresses per environment (`egressIps` above): two that are in use, and two disaster-recovery addresses that carry traffic only after a failover to the second region. Allow all four, so that deliveries keep arriving after a failover. They are used for: * **Webhook deliveries** to your webhook endpoints ([Webhooks](/webhooks/overview)). * **OTP emails** sent through your SMTP server, if you use [email OTP links](/consumer/email-otp-links). If your webhook receiver or SMTP server filters inbound traffic, allow all egress addresses of the environment you use, including the disaster-recovery addresses. ### Egress address tables The tables for sandbox and production, with each address's status (`active`: in use; `announced`: used from its `since` date; `retiring`: still in use until its `removeAfter` date), are published here from AllRide's infrastructure configuration. Status and dates are shown only in these tables; the developer reference lists the current set. > [!NOTE] > > The tables appear here after the first network sync. Until then, `egressIps` in the developer reference lists the current addresses, and every change is announced by email to your technical contact. ## Change policy * New egress addresses are announced at least **30 days** before AllRide starts using them. * Addresses being retired stay in use for at least **30 days** after the announcement. * The API hosts have no published inbound addresses (see above); only the hostnames are stable. * Every change is announced by email to your technical contact (`contacts.technicalEmail`) and in a banner on this site, and the developer reference shows the new state. Allow announced addresses as soon as they are announced, and remove retired ones only after their removal date (`removeAfter` in the [egress address tables](#egress-address-tables)); the announcement email gives the same dates. ## TLS requirements * The API accepts only HTTPS with **TLS 1.2 or newer** and requires SNI. Plain-HTTP requests to the API host are refused with 403 and never redirected; always use `https://` so that your key is never sent unencrypted. * The API hosts are served through Cloudflare and present a publicly trusted certificate that is managed and renewed automatically. **Do not pin** the server certificate or its intermediate: validate it against your system trust store. * Webhook deliveries connect to your endpoint over TLS 1.2 or newer and validate your server certificate against its hostname. Self-signed server certificates are not accepted. * The API authenticates your client with its API key over HTTPS; client certificates (mutual TLS) are not used. To restrict where your keys work from, use the [IP allowlist](/guides/ip-allowlist). ## Related * [IP allowlist](/guides/ip-allowlist) * [Authentication](/guides/authentication) * [Webhooks](/webhooks/overview) * [Sandbox and production](/guides/environments) # Pagination, filtering and sorting Source: https://allride-voucher-consumer.mintlify.app/guides/pagination Page through lists with page and perPage, sort, search and filter them, sync large collections incrementally and download large lists. Every list operation (for example [`GET /consumer/v1/redemptions`](/api-reference/consumer-redemptions-list)) takes the same query parameters. Each reference page lists exactly which sort fields, search fields and filters that operation supports. ## Pages | Parameter | Default | Allowed | | - | - | - | | `page` | `1` | 1 to 100,000 | | `perPage` | `25` | 1 to 100 | The response is a page envelope: ```json theme={null} { "data": { "data": [], "page": 3, "perPage": 25, "totalPages": 5, "total": 112 } } ``` * `page` and `perPage` echo the values that were applied. * `total` is the exact number of matching items; `totalPages` is `ceil(total / perPage)` and `0` when nothing matches. * A `page` beyond `totalPages` is not an error: you get `200` with an empty `data` array and the real totals. * There are no `next` or `prev` links. Compute the next page from `page` and `totalPages`. * A `page` or `perPage` outside the allowed range is answered with `400 data-failed-validation`: the `more.validationErrors` item has `property` `page` or `perPage`, the constraint [`invalid-page`](/errors/catalog#invalid-page) and `params.min` / `params.max`. ## Sorting `sort` is **one** field, optionally prefixed with `-` for descending order (`sort=field` or `sort=-field`): ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/consumer/v1/redemptions" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "sort=-redeemedAt" ``` * Use one of the fields the operation allows. Any other value, a comma-separated list included, is answered with `400 data-failed-validation`: the item has `property` `sort`, the constraint [`invalid-sort`](/errors/catalog#invalid-sort) and `params.allowed`, the sortable fields. * The default is the resource's natural time field, descending (for example `-createdAt` or `-redeemedAt`). * The server always appends `id` in the direction of your sort field, so the order is total and stable even when several items share a timestamp. * Sorting by a localised text field uses the language of your request. ## Searching `search` is a free-text term of 1 to 255 characters (an empty value is ignored). * It matches case-insensitively as a substring over the operation's search fields, for example a redemption entity's `refId`, `displayName` and `email`. * A term that is a numeric id, or an exact `refId` or `externalRef`, also matches by equality. * `search` and filters are combined with AND. ## Filtering Filters use bracket notation: `filter[][]=`, or `filter[]=` for the default operator. | Operator | Meaning | Field types | | - | - | - | | `eq` (default) | equals | all | | `ne` | not equal | text, enum, number, id | | `in` | one of a comma-separated list | text, enum, number, id | | `gt`, `gte`, `lt`, `lte` | comparisons | number, timestamp, date, month | | `contains` | case-insensitive substring | text | | `startsWith` | case-insensitive prefix | text | | `isNull` | `true`: is null, `false`: is not null | nullable fields | Rules: * Field names are the JSON property names; nested properties use dots (`filter[redemptionEntity.refId]=customer:48213`). * A value with an unescaped comma means `in`: `filter[status]=completed,voided`. Write a literal comma inside a value as `\,` and a backslash as `\\`. * `null` is not a value: use `filter[redemptionLinkId][isNull]=true`. * Several filters are combined with AND; the same field may appear with several operators to form a range. * At most 20 `filter[…]` parameters per request and 100 values per `in` list; more is answered like an invalid filter (below). * Timestamps need an offset (`2026-10-01T00:00:00Z`); ranges are half-open, so combine `gte` and `lt`. * An unknown field, an operator the field does not support or a value of the wrong type is answered with `400 data-failed-validation`: the item's `property` is the parameter (`filter[redeemedAt][gte]`) and its constraint is [`invalid-filter`](/errors/catalog#invalid-filter). A query parameter the operation does not declare at all gets the constraint [`unknown-parameter`](/errors/catalog#unknown-parameter) in the same response. Brackets must be URL-encoded. With curl, let `--data-urlencode` do it: ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/consumer/v1/redemptions" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "filter[redeemedAt][gte]=2026-10-01T00:00:00Z" \ --data-urlencode "filter[redeemedAt][lt]=2026-11-01T00:00:00Z" \ --data-urlencode "filter[status]=completed" \ --data-urlencode "sort=-redeemedAt" \ --data-urlencode "perPage=100" ``` ## Invalid list parameters Every problem with the list parameters of one request is reported in **one** `400` [`data-failed-validation`](/errors/catalog#data-failed-validation) response, with one `more.validationErrors` item per problem: | Constraint | Parameter | `params` | | - | - | - | | [`invalid-page`](/errors/catalog#invalid-page) | `page` (1 to 100,000) or `perPage` (1 to 100) | `min`, `max` | | [`invalid-sort`](/errors/catalog#invalid-sort) | `sort` | `allowed`: the sortable fields | | [`invalid-filter`](/errors/catalog#invalid-filter) | the `filter[…]` parameter | depends on the filter, for example `maxMonths` | | [`unknown-parameter`](/errors/catalog#unknown-parameter) | a parameter the operation does not declare | none | ```json theme={null} { "errors": [ { "message": "One or more properties or parameters are invalid.", "statusCode": 400, "errorCode": "data-failed-validation", "more": { "validationErrors": [ { "property": "sort", "constraint": "invalid-sort", "message": { "en": "Sort by one of the allowed fields." }, "params": { "allowed": ["redeemedAt", "createdAt"] } } ] } } ] } ``` There is no limit on `page × perPage`, but deep pages are slow and shift while rows are added. For large result sets, narrow the list with filters, sync incrementally (below) or walk time windows as described in [Downloading large lists](#downloading-large-lists). ## Syncing large collections Page numbers shift when items are added while you page, so an offset walk can skip or repeat items. To keep a local copy in sync: **1. Filter by a time field and sort by it** Request `filter[redeemedAt][gte]=&sort=redeemedAt&perPage=100` (use the time field the operation offers, for example `redeemedAt` or `createdAt`). **2. Walk the pages** Read pages until `page` equals `totalPages`. For a long backlog, restart every 100 pages with a new `gte` value: the largest timestamp you have seen. **3. Deduplicate by id** `gte` returns the boundary item again; upsert by `id` so repeats do not matter. To react to changes as they happen, subscribe to [webhooks](/webhooks/overview). ## Downloading large lists There are no export endpoints: to get a whole list (for a CSV or spreadsheet file, or a one-off extract), page through the list operation itself. The dashboards' "Download CSV / Excel" action works the same way. * Send `perPage=100` (the maximum on every list) and the **same** filters, search and sort on every request. For a small list, plain paging from `page=1` to `totalPages` is enough. * For a large list (we recommend it above about 100 pages), walk time windows on the list's **download key** instead, so rows added while you download do not shift the pages: `createdAt`, or `redeemedAt` for redemptions (each reference page lists the operators; the key supports `gt`, `gte`, `lt`, `lte`). 1. Fix a cut-off when you start: `filter[redeemedAt][lte]=` and sort by `-redeemedAt`. 2. Read pages 1 to 100. 3. Start again at `page=1` with `filter[redeemedAt][lte]=`, and drop rows whose `id` you already have. Stop when a page has fewer than 100 rows. * Rows created after the cut-off are not included; take them in your next run. * Send one request at a time. On `429` wait for `Retry-After`, then repeat the same request. Requests are limited at the network edge per source IP address (see [Limits → Edge limits](/guides/limits#edge-limits)), and a 50,000-row download (500 requests) stays far below that limit. ```bash theme={null} curl -s "$VOUCHER_API_BASE_URL/consumer/v1/redemptions?perPage=100&page=1&sort=-redeemedAt&filter%5BredeemedAt%5D%5Blte%5D=2026-11-01T06:00:00Z" \ -H "Authorization: Bearer $VOUCHER_API_KEY" ``` ## Related * [Requests and responses](/guides/requests-and-responses) * [Webhooks](/webhooks/overview) * [Limits](/guides/limits) * [Errors](/errors) # Permissions and scopes Source: https://allride-voucher-consumer.mintlify.app/guides/permissions How permissions decide what a user or API key may do, how to choose API key scopes, and the full permission reference. ## How permissions work Every operation requires a set of **permissions**, named in kebab case after what they allow (`view-consumer-catalog`, `create-consumer-redemptions`, `manage-webhooks`). A request is allowed when its principal holds **all** of the operation's permissions. * **Dashboard users** hold the permissions of all their groups plus the permissions given to them directly, within one partner ([Members and groups](/workspace/members-and-groups)). * **API keys** hold exactly their **scopes**, chosen when the key is created. A key never inherits the permissions of the user who created it. * Some permissions are **not delegable**: they cover identity and security administration, payment authorisation and accepting prices, and can never be API-key scopes. Operations that need one can only be called by signed-in dashboard users. A request without a required permission is refused with `403` [`missing-permission`](/errors/catalog#missing-permission); `more.requiredPermissions` lists what the operation needs: ```json theme={null} { "errors": [ { "message": "The membership or API key lacks a permission the operation requires, including non-delegable permissions called with an API key.", "statusCode": 403, "errorCode": "missing-permission", "more": { "requiredPermissions": ["void-consumer-redemptions"] } } ] } ``` Each page of the [API reference](/api-reference/consumer-redemptions-create) ends with a line such as **Required permissions:** `create-consumer-redemptions` · **API keys:** allowed. A few workspace reads (for example [workspace branding](/api-reference/partner-workspace-branding-get)) show "none (any member or API key)". Operations marked **API keys: not allowed** display the note "Dashboard session only". [`GET /partner/v1/principal`](/api-reference/partner-principal-get) returns the `effectivePermissions` of the calling key or user, and [`GET /partner/v1/permissions`](/api-reference/partner-permissions-list) lists every permission that applies to your partner. ## Choosing scopes Give each key the smallest set of scopes its system needs, and use separate keys for separate systems. | Integration | Scopes | | - | - | | Redeem vouchers in your app or backend | `view-consumer-catalog`, `create-consumer-redemptions`, `view-consumer-redemptions` | | Manage customers as redemption entities (including GDPR erasure) | add `view-consumer-redemption-entities`, `manage-consumer-redemption-entities`, `erase-consumer-redemption-entities` | | Void redemptions (for example on refunds) | add `void-consumer-redemptions` | | Create Redemption Links | `view-consumer-catalog`, `view-consumer-redemption-links`, `manage-consumer-redemption-links` | | Sync the catalog and set toggles | `view-consumer-catalog`, `manage-consumer-catalog` | | Receive webhooks (manage endpoints, read delivery logs) | `view-webhooks`, `manage-webhooks`, `view-webhook-deliveries` | | Fetch invoices into your ERP | `view-consumer-billing` | | Check price acceptance before offering a set | `view-consumer-pricing` | | Reporting | `view-consumer-analytics`, `view-consumer-redemptions` | Subscribing a webhook endpoint to billing events also needs `view-consumer-billing`, and to pricing events `view-consumer-pricing`, on the key or user that creates or edits the endpoint. ## Permission reference **Workspace permissions** (every partner) | Permission | Allows | Delegable to API keys | | - | - | - | | `view-profile` | Read your partner record and settings | yes | | `manage-settings` | Edit contacts, locale, time zone, MFA policy, IP allowlist enforcement | no | | `view-members` | List members and invitations | yes | | `manage-members` | Invite, suspend and remove members; edit their direct permissions | no | | `view-groups` | List groups | yes | | `manage-groups` | Create, edit and delete groups and their members | no | | `view-audit` | Read the audit log | yes | | `request-api-access` | Request API access | no | | `view-api-keys` | List API keys (metadata only) | no | | `manage-api-keys` | Create, roll and revoke API keys | no | | `view-webhooks` | List webhook endpoints | yes | | `manage-webhooks` | Create, edit, pause and delete endpoints, rotate secrets, send test events and replay deliveries | yes | | `view-webhook-deliveries` | Read the webhook delivery log | yes | | `view-ip-allowlist` | Read the IP allowlist | yes | | `manage-ip-allowlist` | Change the IP allowlist | no | | `view-domains` | Read custom domains | yes | | `manage-domains` | Add, re-check and delete custom domains | yes | | `manage-workspace-branding` | Edit Workspace Branding and brand assets | yes | | `view-sso` | Read single sign-on connections | no | | `manage-sso` | Manage single sign-on connections and domain verification | no | **Consumer permissions** | Permission | Allows | Delegable to API keys | | - | - | - | | `view-consumer-catalog` | Read your catalog | yes | | `manage-consumer-catalog` | Provider and Voucher Set Toggles, positions, featured flags, category overrides, settings, entity limits and your references | yes | | `view-consumer-redemptions` | Read redemptions, including issued codes and links | yes | | `create-consumer-redemptions` | Redeem vouchers (single and batch) | yes | | `void-consumer-redemptions` | Void redemptions | yes | | `view-consumer-redemption-entities` | Read redemption entities (including the email of entities created by the Email OTP Gate) | yes | | `manage-consumer-redemption-entities` | Create, update, block and unblock redemption entities | yes | | `erase-consumer-redemption-entities` | Erase redemption entities (GDPR) | yes | | `view-consumer-redemption-links` | Read Redemption Links | yes | | `manage-consumer-redemption-links` | Create, update and revoke Redemption Links | yes | | `manage-consumer-email` | SMTP configuration, email templates and the email OTP setting | yes | | `view-consumer-analytics` | Read statistics | yes | | `view-consumer-billing` | Read the billing account, contacts, payment methods (masked), invoices (Stornorechnungen included), payments, usage and balance; download documents | yes | | `manage-consumer-billing` | Edit the billing account and contacts, set up or remove SEPA mandates, resend documents | no | | `view-consumer-pricing` | Read your Price Terms, upcoming price changes and Price Acceptances | yes | | `accept-consumer-pricing` | Accept Price Terms (signed-in users only, with a recent sign-in) | no | **Default groups** Every consumer partner starts with these groups. You can create your own groups as well ([Members and groups](/workspace/members-and-groups)). | Group | Permissions | | - | - | | `owners` | All permissions. Cannot be changed | | `administrators` | All permissions except `manage-sso` and `request-api-access` | | `developers` | `view-profile`, `view-audit`, `request-api-access`, `view-api-keys`, `manage-api-keys`, `view-webhooks`, `manage-webhooks`, `view-webhook-deliveries`, `view-ip-allowlist`, `manage-ip-allowlist`, `view-domains`, and every other `view-*` permission except `view-consumer-billing` and `view-consumer-pricing` | | `redemption-operators` | `view-profile`, `view-consumer-catalog`, `view-consumer-redemptions`, `create-consumer-redemptions`, `void-consumer-redemptions`, `view-consumer-redemption-entities`, `manage-consumer-redemption-entities`, `view-consumer-redemption-links`, `manage-consumer-redemption-links` | | `billing` | `view-profile`, `view-consumer-billing`, `manage-consumer-billing`, `view-consumer-analytics`, `view-consumer-pricing`, `accept-consumer-pricing` | | `viewers` | Every `view-*` permission except `view-api-keys`, `view-consumer-billing` and `view-consumer-pricing` | ## Related * [API keys](/guides/api-keys) * [Members and groups](/workspace/members-and-groups) * [Authentication](/guides/authentication) # Requests and responses Source: https://allride-voucher-consumer.mintlify.app/guides/requests-and-responses JSON format, response envelopes, numeric ids, timestamps, money, localised text, concurrent changes, commands and asynchronous jobs. Every operation of the Consumer API (`/consumer/v1`) and the Workspace API (`/partner/v1`) follows the conventions on this page. Learn them once and every reference page reads the same way. ## Format * Requests and responses are JSON in UTF-8. Send `Content-Type: application/json` with every request that has a body. * The API is HTTPS only (TLS 1.2 or later). Production is `https://api.voucher.allride.io`, the sandbox is `https://api.voucher-staging.allride.io`. * Property names are `camelCase`; enum values are `kebab-case` (`deactivation-scheduled`). * Nullable properties are **always present**. A value that is not set is `null`, never a missing key, so you can rely on the shape of every resource. * Send `Accept-Language: de` or `en` to choose the language of server-formatted text such as money labels and error messages. The response carries `Content-Language`. * Every response carries `X-Request-Id`. You can send your own `X-Request-Id` (1-100 characters of letters, digits and `._:-`) to correlate requests with your logs. ## Response envelopes A response that returns one resource wraps it in `data`: ```json theme={null} { "data": { "id": 6017, "resource": "redemption", "status": "completed", "voucherSetId": 2030, "redeemedAt": "2026-10-21T17:42:10.120Z", "_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" } } } } ``` A list returns a page inside `data`: ```json theme={null} { "data": { "data": [ { "id": 6017, "resource": "redemption", "status": "completed", "_links": { "self": { "href": "https://api.voucher.allride.io/consumer/v1/redemptions/6017", "type": "application/json" } } } ], "page": 1, "perPage": 25, "totalPages": 5, "total": 112 } } ``` The other shapes: | Response | Shape | | - | - | | `200` / `201` with one resource | `{"data": {…}}` | | `200` list | `{"data": {"data": [...], "page", "perPage", "totalPages", "total"}}` | | `202` asynchronous job | `{"data": {…job…}}` plus `Location` and `Retry-After` headers | | `204` delete | No body | | `4xx` / `5xx` error | `{"errors": [{"message", "statusCode", "errorCode", …}]}`, see [Errors](/errors) | Every resource has `id`, `resource` (its type, for example `redemption` or `redemption-link`) and `_links`. `_links` maps a relation name to `{href, type}`; `self` is always present. A link appears only when your key or user holds the permissions to follow it. Value objects and statistics rows (for example analytics rows) carry `resource` but no `id` or `_links`. ## Identifiers * Ids are positive integers (`"id": 42`), unique per resource type. They are JSON numbers in bodies and plain numbers in paths (`/consumer/v1/redemptions/6017`). * An id that is not a positive integer, in the path, the query or the body, is answered with `400 data-failed-validation`; the entry in `more.validationErrors` has the constraint [`invalid-id`](/errors/catalog#invalid-id) and names the parameter or property. * A well-formed id that does not exist **or belongs to another partner** is answered with `404` and the resource's not-found code, for example [`redemption-not-found`](/errors/catalog#redemption-not-found). You never see another partner's data, not even its existence. * Your own references use separate fields: `refId` on redemption entities (1-128 characters of letters, digits and `._:@/+=-`) and `externalRef` on catalog settings (at most 64 characters). Never put personal data into them. ## Dates and times * **Timestamps** are RFC 3339 in UTC with `Z`, at millisecond precision, truncated: no fraction when the milliseconds are zero (`2026-11-01T00:00:00Z`), otherwise exactly three digits (`2026-10-21T17:42:10.120Z`). Accept both shapes. * **Timestamps you send** must carry an offset (`Z` or `+01:00`); they are normalised to UTC. `2026-12-31T23:59:59+01:00` is stored and returned as `2026-12-31T22:59:59Z`. A timestamp without an offset fails with the constraint [`invalid-timestamp`](/errors/catalog#invalid-timestamp). * **Dates** are `YYYY-MM-DD`, **months** `YYYY-MM`. * **Calendar periods** (analytics months and days, per-entity monthly limits) are evaluated in your partner `timezone`, an IANA id such as `Europe/Berlin`. * **Exception for billing:** billing service months and billing dates (`issueDate`, `dueDate`, `collectionDate`) are calendar months and dates in `Europe/Berlin`, whatever your partner timezone. Billing resources that carry a month also carry `billingZone: "Europe/Berlin"`. A redemption at `2026-10-31T23:30:00Z` by a partner in `Europe/London` counts in analytics month `2026-10` and in billing service month `2026-11`. See [Billing](/consumer/billing). * **Durations** are integers whose unit is in the property name: `gracePeriodMinutes`, `expiresInMinutes`, `paymentTermsDays`. * **Intervals** are half-open: `[from, to)`. Filter ranges with `gte` and `lt`. ## Money All amounts are in euros (EUR). `value` is in cent. Money is an object with the amount in cent and a formatted label: ```json theme={null} { "value": 2900, "label": "29,00 €" } ``` * `value` is an integer, never a float. `label` is formatted in the response language (`€29.00` in English, `29,00 €` in German). * In requests send `{"value": 2900}`. A `label` you send is accepted and ignored, so you can send back what you read. * Amounts that can be negative (Stornorechnungen, balances) use the same shape with a signed `value`, for example `{"value": -1250, "label": "-12,50 €"}`. * Per-redemption **unit prices** in billing and pricing add a `scale` property: `{value, scale, label}` with `scale` always `4` and `value` in **ten-thousandths** of a euro. `1000` is €0.10 and `1250` is €0.1250: ```json theme={null} { "value": 1250, "scale": 4, "label": "0,1250 €" } ``` Totals on invoices are always ordinary money in cent. Tax rates are decimal strings with two fraction digits (`"19.00"`), never JSON numbers. See [Billing](/consumer/billing) and [Prices and price acceptance](/consumer/prices). ## Localised text Texts that providers and partners write per language (voucher set names, terms, landing texts) are objects keyed by locale: ```json theme={null} { "de": "5 € Fahrguthaben", "en": "€5 ride credit" } ``` * Responses always return the full map. Pick the locale for your customer on your side. * When you send localised text, include your partner's default locale. * Rich text fields use a restricted Markdown subset (paragraphs, line breaks, bold, italic, lists, headings level 3-4 and https links). Render them with a sanitising Markdown renderer, never as raw HTML. ## Large integers Ids and large counters (`maxRedemptions`, `redemptionCount`) are unsigned 64-bit integers. They are JSON numbers and can exceed 2^53, the largest integer a JavaScript `number` represents exactly. In JavaScript, parse responses with a BigInt-safe JSON parser (for example `json-bigint`) or keep these values as strings. In the OpenAPI document these fields have `format: uint64`; configure your client generator to map them to a 64-bit or arbitrary-precision type. ## Updating resources * `PATCH` is a JSON Merge Patch: a property you leave out stays unchanged, `null` clears it, any other value replaces it. * `PUT` replaces the whole resource (for example `PUT /partner/v1/smtp-configuration`). * `DELETE` answers `204` without a body. * Request bodies are strict: a property the operation does not define is refused with the constraint [`unknown-field`](/errors/catalog#unknown-field), and an undeclared query parameter with [`unknown-parameter`](/errors/catalog#unknown-parameter). Read-only properties such as `id` or `label` in money are the only values you may echo back. ## Concurrent changes Updates are **last-writer-wins per field**. Resources carry no version number, responses carry no entity tag, and there are no conditional request headers: you never send a precondition with a write. * An update writes only the properties you sent. Two clients that change different properties of the same resource both succeed, and neither overwrites the other. * When two clients change the **same** property at the same time, the later write wins. If you need one writer, coordinate it on your side. * A command that changes the state checks the current state inside the same transaction. If another request changed it in the meantime, the command answers `409` with a code that names the state it found (a **state race**). On this site these are, for example: | Code | When | | - | - | | [`invalid-state-transition`](/errors/catalog#invalid-state-transition) | The resource is no longer in a state that allows the command (for example `:revoke` on a redemption link that another request revoked first) | | [`redemption-already-voided`](/errors/catalog#redemption-already-voided) | Another request voided the redemption first | | [`redemption-entity-blocked`](/errors/catalog#redemption-entity-blocked) / [`redemption-entity-erased`](/errors/catalog#redemption-entity-erased) | The entity was blocked or erased while you redeemed or created a link for it | | [`redemption-link-exhausted`](/errors/catalog#redemption-link-exhausted) | The last redemption of the link was used by a parallel request | | [`voucher-set-depleted`](/errors/catalog#voucher-set-depleted) / [`voucher-set-unavailable`](/errors/catalog#voucher-set-unavailable) | The set ran out of stock or became unavailable between your catalog read and the redemption | Read the resource again before you decide what to do; retrying the same request gives the same answer. * If the database could not complete your write because of a concurrent one (a deadlock or a serialization failure), you get `409` [`concurrent-update`](/errors/catalog#concurrent-update); retry with backoff and the same `Idempotency-Key`. This is the only transient conflict code; there is no per-resource concurrency error. ## Commands and batch operations * Actions that are not plain updates are **commands**: `POST` to the resource path with a colon and a verb, for example `POST /consumer/v1/redemptions/{redemptionId}:void` or `POST /consumer/v1/redemption-links/{redemptionLinkId}:revoke`. * Bulk operations are `POST` to the collection with `:batch`, for example [`POST /consumer/v1/redemptions:batch`](/api-reference/consumer-redemptions-batch). Batches are **all or nothing**: if one item fails, nothing is created, and the error names the item (`items[2]`) in `more.validationErrors`. * Batch results use the single-object envelope around one result resource whose `items` keep the order of your request. * Every `POST`, commands and batches included, accepts an [`Idempotency-Key`](/guides/idempotency). ## Asynchronous jobs No operation on the consumer site runs as an asynchronous job: every request answers when its work is done, and there are no export jobs (to get a whole list, see [Downloading large lists](/guides/pagination#downloading-large-lists)). Asynchronous jobs exist only on the provider site (voucher uploads) and follow this pattern: **1. Start the job** The command answers `202 Accepted` with the job in its initial status, a `Location` header with the job URL and `Retry-After: 2`. **2. Poll the job** `GET` the `Location` URL after `Retry-After` seconds. Double the interval on each poll, up to 30 seconds. **3. Use the result** When the job's status is final, read its result. An idempotent replay of the `202` shows the job **as it was when it was accepted**. Always follow `Location` for the current state. ## Compatibility rules Within `v1` the API changes only in backwards-compatible ways (see [Versioning and deprecation](/guides/versioning)). To keep working when it does, your client must: * ignore response properties and `_links` relations it does not know; * treat enum values it does not know as "other" (every enum is an open set); * ignore members of an error entry or its `more` object that it does not know; * ignore webhook event types it has not subscribed to or does not know. When you contact support about a failed request, include the `X-Request-Id` of the response (for server errors it is also in `requestId`). ## Related * [Pagination, filtering and sorting](/guides/pagination) * [Idempotency](/guides/idempotency) * [Errors](/errors) and the [error catalog](/errors/catalog) * [Versioning and deprecation](/guides/versioning) * [Billing](/consumer/billing) # Versioning and deprecation Source: https://allride-voucher-consumer.mintlify.app/guides/versioning The major version in the path, which changes happen without a new version, and how deprecations and sunsets are announced. ## Version in the path Each surface carries its major version in the path: `/consumer/v1` and `/partner/v1`. Everything in these docs describes `v1`. A future `v2` would run side by side with `v1` under its own path, with its own OpenAPI document and its own version in these docs. ## Changes without a new version Within `v1` the API changes only additively. Expect, without notice in advance: * new operations, commands and webhook event types; * new optional request properties and query parameters; * new response properties and `_links` relations; * new enum values (every enum is an open set); * new error codes and new members in an error's `more` object; * relaxed validation (longer maximum lengths, more accepted values). Your client must therefore ignore unknown response properties, relations and error members, treat unknown enum values as "other", and ignore webhook types it does not handle. Additions appear in the API reference. ## Breaking changes and new versions These changes never happen within `v1`; they only come with a new major version: * removing or renaming an operation, property, parameter, header or relation; * making an optional input required or tightening the validation of an existing field; * changing a property's type, format, unit, meaning or nullability; * removing an enum value or rejecting a value that was accepted; * changing the status code or `errorCode` of an existing outcome; * changing envelopes, pagination defaults or id formats; * changing authentication or adding a required permission to an existing operation. When a new major version is released, the old one stays available for **at least 12 months** after the new one is generally available. ## Deprecation and sunset A deprecated operation, or every operation of a deprecated major version, answers with these headers: ```http theme={null} Deprecation: @1798761600 Sunset: Sat, 01 Jan 2028 00:00:00 GMT Link: ; rel="deprecation"; type="text/html" Link: ; rel="successor-version" ``` * `Deprecation` (RFC 9745) is the moment the deprecation took effect, in Unix seconds. * `Sunset` (RFC 8594) is when the operation stops working. * `Link` points to the [deprecation and sunset](#deprecation-and-sunset) rules on this page and, where there is one, to the successor. Which operations are deprecated is shown by `deprecated: true` on the operation in the API reference and in the deprecation email to your technical contact. Minimum notice between deprecation and sunset: | What is deprecated | Notice | | - | - | | A whole major version | 12 months | | A single operation that has a successor in the same version | 6 months | Deprecated operations are marked on their reference page with the sunset date. After the sunset, the operation answers `410` [`api-version-sunset`](/errors/catalog#api-version-sunset) for 90 days (`more.successorUrl` names the successor), then `404 route-not-found`. Log the `Deprecation` and `Sunset` headers in your client so you notice a deprecation even if you missed the announcement. ## Webhook versions Webhook bodies carry no version, and a webhook endpoint is not bound to one. The body is always `{"event": {"id", "type", "timestamp"}, "payload": {…}}`, and a payload only gains fields: ignore properties you do not know. A breaking change to a payload ships as a **new event type**, which you subscribe to when you are ready; the existing type keeps its shape. ## Staying informed * Keep `contacts.technicalEmail` on your partner a monitored mailbox. Every deprecation and sunset is announced there, together with network address changes and sandbox resets. * Keep your partner's technical contact current in the developer dashboard: deprecation notices are emailed to it when your integration still calls a deprecated operation. * Deprecations with a sunset within 90 days are also announced in a banner on this site. ## Related * [Requests and responses](/guides/requests-and-responses#compatibility-rules) * [Webhooks](/webhooks/overview) * [Going-live checklist](/guides/going-live) # Introduction Source: https://allride-voucher-consumer.mintlify.app/index Build voucher offers into your apps and channels with the AllRide Vouchers Consumer API. The AllRide Voucher Platform connects **Partner Providers** (for example scooter, bike or car-sharing operators) that publish vouchers with **Partner Consumers** that offer those vouchers to their own end customers. As a Partner Consumer you use the Consumer API to read the voucher sets AllRide makes available to you, redeem vouchers for your customers, hand out Redemption Links, and follow everything through webhooks, analytics and billing. Everything the consumer dashboard does goes through this same API. If you can do it in the dashboard, you can automate it, with a small set of security and billing actions reserved for signed-in dashboard users. ## What you can build * **Redeem in your own app**: redeem a voucher with one `POST /consumer/v1/redemptions` call and show the issued code or link to your customer ([Redeeming vouchers](/consumer/redeeming)). * **Share Redemption Links**: send a link by email, SMS or in your app; the customer picks a voucher on a landing page in your branding ([Redemption links](/consumer/redemption-links)). * **Curate your catalog**: turn providers and voucher sets on or off, order and feature them, and keep your own references ([Catalog and toggles](/consumer/catalog)). * **Gate links by email**: let customers prove their email address with a one-time code sent through your own SMTP server ([Email OTP links](/consumer/email-otp-links)). * **Track and reconcile**: read redemption statistics, page through lists and receive webhooks for every redemption ([Analytics](/consumer/analytics)). * **Automate billing**: fetch invoices, Stornorechnungen and e-invoices into your ERP with an API key ([Billing](/consumer/billing)). ## Environments You build and test in the **sandbox** and go live in **production**. They are separate environments with separate partner accounts, API keys, webhook endpoints and data. | | Sandbox | Production | | - | - | - | | API base URL | `https://api.voucher-staging.allride.io` | `https://api.voucher.allride.io` | | Developer dashboard | [consumer-developers.voucher-staging.allride.io](https://consumer-developers.voucher-staging.allride.io/go/overview) | [consumer-developers.voucher.allride.io](https://consumer-developers.voucher.allride.io/go/overview) | | API key prefix | `vk_test_` | `vk_live_` | | Data | Test data, never real voucher codes | Real | The addresses of both environments are listed on [Network and IP addresses](/guides/network). [Sandbox and production](/guides/environments) explains the differences. ## How the API is organised The Consumer API has two surfaces, both under the same host and both documented on this site: * **Consumer API** (`/consumer/v1`): your catalog and catalog settings, redemptions, redemption entities, Redemption Links, email OTP settings, analytics, billing and pricing. * **Workspace API** (`/partner/v1`): everything about your partner workspace that providers and consumers share: partner record and settings, members and groups, API keys, webhook endpoints and delivery logs, IP allowlist, custom domains, branding, single sign-on and audit log. The SMTP configuration and email templates for the Email OTP Gate also live here. The version is part of the path (`/consumer/v1`), and every request: * authenticates with `Authorization: Bearer ` ([Authentication](/guides/authentication)); * sends and receives JSON; single resources come as `{"data": {…}}` and lists as a page envelope ([Requests and responses](/guides/requests-and-responses)); * may carry an `Idempotency-Key` on POST so that retries are safe ([Idempotency](/guides/idempotency)); * answers errors as `{"errors": [{"message", "statusCode", "errorCode"}]}` with a stable `errorCode` ([Errors](/errors)). The [API reference](/api-reference/consumer-redemptions-create) lists every operation with its required permissions and whether API keys may call it. The [webhook event catalog](/webhooks/events) lists every event you can subscribe to. ## Get help * Start with the [quickstart](/quickstart): it takes you from an API key to a redemption and a verified webhook in the sandbox. * Look up any `errorCode` in the [error catalog](/errors/catalog); it says what happened and what to do. * New features, deprecations and network changes are announced by email to your technical contact (`contacts.technicalEmail`). * For anything else, contact your AllRide account manager or AllRide through the **Support** link at the top of this site. Include the `X-Request-Id` response header (or the `requestId` of a 5xx error), the time of the request and the operation you called. Never send API keys, webhook secrets or customer data. ## Using these docs with AI coding agents These docs are available in machine-readable form, so you can give an AI coding assistant accurate context: * [`/llms-full.txt`](https://docs-consumer.voucher.allride.io/llms-full.txt) contains every page of this site as one Markdown file; [`/llms.txt`](https://docs-consumer.voucher.allride.io/llms.txt) is the index. * Every page is also available as Markdown: add `.md` to its URL, or use the page menu to copy it or open it in an assistant. * Download the OpenAPI 3.1 document of the Consumer and Workspace API as [`consumer.v1.json`](https://github.com/AllRide-Mobility/AllRideVoucherPartnerConsumerAPIDocs/releases/latest/download/consumer.v1.json). This link always serves the version published on this site. AllRide publishes no official SDKs. Generate a client from the OpenAPI document with any OpenAPI 3.1 generator. Ids and some counters are unsigned 64-bit integers (`format: uint64`) that can exceed 2^53, so in JavaScript parse responses with a BigInt-safe JSON parser. ## Related * [Quickstart](/quickstart) * [Concepts](/guides/concepts) * [Going-live checklist](/guides/going-live) # Quickstart Source: https://allride-voucher-consumer.mintlify.app/quickstart Get a sandbox API key, list your catalog, redeem a voucher, replay the request safely and receive a signed webhook. This quickstart runs entirely in the **sandbox**. It takes about 15 minutes and uses `curl` and `jq`. Every request here is a real request against `https://api.voucher-staging.allride.io`; nothing you do affects production. **1. Get sandbox access and an API key** 1. **Get a sandbox account.** If you have no sandbox sign-in yet, send your AllRide account manager (or AllRide through the **Support** link at the top of this site) your company name and the email address of your first user. AllRide creates your consumer partner in the sandbox and sends that user an invitation. 2. Sign in to the [sandbox developer dashboard](https://consumer-developers.voucher-staging.allride.io/go/api-access) and request API access for your consumer partner. AllRide activates it; you get an email when it is active. 3. Open [API keys](https://consumer-developers.voucher-staging.allride.io/go/api-keys) and create a key with these scopes: * `view-consumer-catalog` * `create-consumer-redemptions` * `view-consumer-redemptions` * `manage-webhooks` 4. Copy the key now: it starts with `vk_test_` and is shown only once. Export the base URL and the key in your shell: ```bash theme={null} export VOUCHER_API_BASE_URL="https://api.voucher-staging.allride.io" export VOUCHER_API_KEY="" ``` Check that the key works and has the scopes you expect: ```bash theme={null} curl -s "$VOUCHER_API_BASE_URL/partner/v1/principal" \ -H "Authorization: Bearer $VOUCHER_API_KEY" | jq '.data | {partnerSlug, environment, effectivePermissions}' ``` `environment` is `test` in the sandbox. A `401` with `errorCode` `api-key-invalid` means the key is mistyped or belongs to production; a `403` with `api-access-inactive` means API access is not active yet. **2. List your catalog** Ask for the voucher sets that are active and switched on for your channels: ```bash theme={null} curl -s -G "$VOUCHER_API_BASE_URL/consumer/v1/catalog/voucher-sets" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "filter[availability]=active" \ --data-urlencode "filter[providerToggle]=true" \ --data-urlencode "filter[voucherSetToggle]=true" \ --data-urlencode "search=Sandbox ride credit" \ | jq '.data.data[] | {id, name: .live.name.en, provider: .provider.slug, stockStatus, priceAcceptance}' ``` The sandbox seeds the provider `sandbox-scooters` with the voucher set **Sandbox ride credit** and switches that provider on for every new consumer. Note the set's `id`: ```bash theme={null} export VOUCHER_SET_ID= ``` Each entry carries `priceAcceptance.status`. A set is redeemable only after a user of your partner accepted its Price Terms. The sandbox sets show `priceAcceptance.status: acceptance-required`: they carry a €0 sandbox price that is never billed. Open **Prices** in the sandbox consumer dashboard and accept it once before you redeem ([Prices and price acceptance](/consumer/prices)). **3. Redeem a voucher** Redeem one voucher for a customer of yours. The inline `redemptionEntity` creates (or reuses) a [redemption entity](/consumer/redemption-entities) with your own reference, and the `Idempotency-Key` makes the request safe to retry: ```bash theme={null} export IDEMPOTENCY_KEY=$(uuidgen) curl -s -i -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemptions" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -d "{\"voucherSetId\": $VOUCHER_SET_ID, \"redemptionEntity\": {\"refId\": \"customer:42\"}}" ``` The response is `201 Created` with the issued voucher in `issued`: ```json theme={null} { "data": { "id": 6017, "resource": "redemption", "status": "completed", "channel": "api", "voucherSetId": 2030, "issued": { "kind": "code", "code": "SANDBOX-7KQ2-M9XA", "link": null }, "validFrom": null, "validUntil": "2026-12-31T22:59:59Z", "redemptionEntity": { "id": 9017, "resource": "redemption-entity", "refId": "customer:42", "_links": { "self": { "href": "https://api.voucher-staging.allride.io/consumer/v1/redemption-entities/9017", "type": "application/json" } } }, "redeemedAt": "2026-10-26T09:15:00.120Z", "_links": { "self": { "href": "https://api.voucher-staging.allride.io/consumer/v1/redemptions/6017", "type": "application/json" } } } } ``` The example is shortened; [Create a redemption](/api-reference/consumer-redemptions-create) shows every field. The issued code is a secret for your customer: show it only to the customer it was issued to. A `409` [`price-acceptance-required`](/errors/catalog#price-acceptance-required) here means the previous step was skipped: accept the sandbox price in **Prices**, then send the request again with a new `Idempotency-Key`. **4. Repeat the identical request** Send exactly the same request again, with the same `Idempotency-Key`: ```bash theme={null} curl -s -i -X POST "$VOUCHER_API_BASE_URL/consumer/v1/redemptions" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -d "{\"voucherSetId\": $VOUCHER_SET_ID, \"redemptionEntity\": {\"refId\": \"customer:42\"}}" ``` You get the same `201` body, including the same `issued` code, and the header `Idempotent-Replayed: true`. No second voucher was redeemed. This is how you retry after a timeout or a network error without redeeming twice ([Idempotency](/guides/idempotency)). **5. Receive and verify a webhook** Register a publicly reachable HTTPS endpoint you control (port 443 or 8443) for `redemption-created`: ```bash theme={null} export WEBHOOK_URL="https://hooks.example-transit.example.com/allride-voucher" ``` ```bash theme={null} curl -s -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d "{\"url\": \"$WEBHOOK_URL\", \"eventTypes\": [\"redemption-created\"]}" \ | jq '.data | {id, firstSecret, secondSecret}' ``` Store `firstSecret` and `secondSecret` in your secret manager: they are shown only in this response. Then send a test event to the endpoint: ```bash theme={null} export WEBHOOK_ENDPOINT_ID= curl -s -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID:send-test-event" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Your endpoint receives a `webhook-endpoint-test` event with the header `x-signing-signature: t=…,s1=…,s2=…`. Verify it as described in [Verifying signatures](/webhooks/verify-signatures) and answer with any `2xx` status within 15 seconds. From now on, every redemption of your partner also sends a signed [`redemption-created`](/webhooks/events/redemption-created) event to this endpoint. **6. Next steps** * Hand out vouchers without building a UI: [Redemption links](/consumer/redemption-links). * Model your customers or accounts and set per-customer limits: [Redemption entities](/consumer/redemption-entities). * Make your integration production-ready: [Going-live checklist](/guides/going-live). ## Related * [Authentication](/guides/authentication) * [Redeeming vouchers](/consumer/redeeming) * [Webhooks](/webhooks/overview) # Event catalog Source: https://allride-voucher-consumer.mintlify.app/webhooks/events Every webhook type a Partner Consumer can subscribe to, with its trigger and payload. These are the 25 webhook types a Partner Consumer can subscribe to. Each type links to its page with the full payload schema and an example body. Every body has the same shape (`event` with `id`, `type` and `timestamp`, and `payload` starting with `resource` and `id`; see [Payload format](/webhooks/overview#payload-format)) and is signed as described in [Verifying signatures](/webhooks/verify-signatures). * `catalog-*` events are sent for every voucher set that is visible to you, whether or not you turned it on in your catalog settings, so you learn about new sets you may want to enable. * Billing types (`invoice-*`, `payment-failed`, `payment-method-deactivated`) need `view-consumer-billing`, and `price-terms-*` types need `view-consumer-pricing`, held by whoever creates or changes the endpoint. * `webhook-endpoint-test` is sent only when you [send a test event](/webhooks/overview#test-events). * New types can be added within `v1`. Ignore types you do not handle and answer `2xx` for them. | Type | Sent when | `payload` | | - | - | - | | [`redemption-created`](/webhooks/events/redemption-created) | A voucher is redeemed (any channel except import) | `Redemption` | | [`redemption-voided`](/webhooks/events/redemption-voided) | A redemption is voided | `Redemption` | | [`redemption-entity-created`](/webhooks/events/redemption-entity-created) | An entity is created in the dashboard or by the email OTP gate | `RedemptionEntity` | | [`redemption-link-exhausted`](/webhooks/events/redemption-link-exhausted) | A link reaches `maxRedemptions` or `maxTotalRedemptions` | `RedemptionLink` | | [`redemption-link-expired`](/webhooks/events/redemption-link-expired) | A link passes `expiresAt` | `RedemptionLink` | | [`catalog-voucher-set-activated`](/webhooks/events/catalog-voucher-set-activated) | The set becomes active | `ConsumerCatalogEntry` | | [`catalog-voucher-set-activation-scheduled`](/webhooks/events/catalog-voucher-set-activation-scheduled) | The provider schedules an activation | `ConsumerCatalogEntry` | | [`catalog-voucher-set-available`](/webhooks/events/catalog-voucher-set-available) | A set becomes visible to you (activation, access granted, provider reactivated) | `ConsumerCatalogEntry` | | [`catalog-voucher-set-deactivated`](/webhooks/events/catalog-voucher-set-deactivated) | The set is deactivated | `ConsumerCatalogEntry` | | [`catalog-voucher-set-deactivation-scheduled`](/webhooks/events/catalog-voucher-set-deactivation-scheduled) | The provider schedules a deactivation | `ConsumerCatalogEntry` | | [`catalog-voucher-set-depleted`](/webhooks/events/catalog-voucher-set-depleted) | `stockStatus` becomes `depleted` | `{resource, id}` | | [`catalog-voucher-set-stock-low`](/webhooks/events/catalog-voucher-set-stock-low) | `stockStatus` becomes `low` | `{resource, id, stockStatus}` | | [`catalog-voucher-set-unavailable`](/webhooks/events/catalog-voucher-set-unavailable) | The set stops being visible to you | `{resource, id, reason}` | | [`catalog-voucher-set-updated`](/webhooks/events/catalog-voucher-set-updated) | New live revision, schedule change, profile or branding change, stock available again | `ConsumerCatalogEntry` + `changedFields` | | [`price-terms-acceptance-required`](/webhooks/events/price-terms-acceptance-required) | a price change started with sets you have not accepted | `{resource, id, startsAt, voucherSetIds}` | | [`price-terms-accepted`](/webhooks/events/price-terms-accepted) | a user of your partner accepts Price Terms | `PriceAcceptance` (consumer view) | | [`price-terms-change-scheduled`](/webhooks/events/price-terms-change-scheduled) | AllRide schedules a change of your Price Terms | `{resource, id, startsAt, voucherSets}` | | [`invoice-finalised`](/webhooks/events/invoice-finalised) | the invoice (also a Stornorechnung) is finalised and its document rendered | `Invoice` (consumer view) | | [`invoice-overdue`](/webhooks/events/invoice-overdue) | the due date passed with an amount due | `Invoice` (consumer view) | | [`invoice-paid`](/webhooks/events/invoice-paid) | the invoice is fully paid | `Invoice` (consumer view) | | [`invoice-voided`](/webhooks/events/invoice-voided) | a Stornorechnung cancels the invoice | `Invoice` (consumer view, status `void`) plus `cancellationInvoiceId` | | [`payment-failed`](/webhooks/events/payment-failed) | a debit or payment attempt fails | payment summary `{resource, id, invoiceId, …}` | | [`payment-method-deactivated`](/webhooks/events/payment-method-deactivated) | the mandate is revoked or fails hard | mandate summary `{resource, id, status, …}` | | [`webhook-endpoint-test`](/webhooks/events/webhook-endpoint-test) | you send a test event | `{resource, id, message}` | # Webhooks Source: https://allride-voucher-consumer.mintlify.app/webhooks/overview Receive signed HTTPS notifications when redemptions, links, catalog entries, prices and invoices change. Webhooks tell your systems when something happens in your partner account, so you do not have to poll the API. AllRide sends an HTTPS `POST` with a JSON body to each of your webhook endpoints that subscribes to the event's type. ## How webhooks work 1. Something happens in the platform: a voucher is redeemed, a Redemption Link runs out, a voucher set becomes available in your catalog, an invoice is issued. 2. AllRide renders the event for your partner (the consumer view of the resource) and **stores** it, with a stable `event.id`, for every webhook endpoint of yours that subscribes to its type. For an active endpoint the delivery appears in your delivery log at once as `PENDING` and the send is queued; for a paused endpoint it is stored as `FAILED` with `endpoint-disabled` and can be replayed after you resume the endpoint. 3. Your endpoint verifies the `x-signing-signature` header, answers `2xx` within 15 seconds and processes the event afterwards. 4. Any other outcome is a failed attempt; the stored event is sent again with backoff, up to 5 attempts in total. The delivery ends as `DELIVERED` (a `2xx`) or `FAILED` (the last attempt failed, or no request could be sent; `lastErrorCode` says why), see [Retries, logs and replay](/webhooks/retries-and-logs#why-a-delivery-failed-lasterrorcode). Every delivery and each of its attempts (status code, response excerpt, time) is kept in the delivery log for 30 days, so you can see what was sent and what your endpoint answered. You can subscribe to the 25 consumer event types listed in the [event catalog](/webhooks/events): redemptions, redemption entities, Redemption Links, catalog changes, Price Terms, billing documents and payments, plus the test event. > [!NOTE] > > Deliveries are made only while your partner is `active` **and** its API access is `active`. Events that occur in other periods are not delivered and cannot be replayed. The endpoint shows the reason in `deliveriesBlockedReason`. ## Register an endpoint Register endpoints in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/webhooks) or through the Workspace API with [Create a webhook endpoint](/api-reference/partner-webhook-endpoints-create). Both need the permission `manage-webhooks`, which you can delegate to API keys. Dashboard users additionally need a recent sign-in. ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d "{ \"url\": \"$WEBHOOK_URL\", \"description\": \"Redemption notifications\", \"eventTypes\": [\"redemption-created\", \"redemption-voided\"] }" ``` The `201` response contains the endpoint and its two signing secrets, `firstSecret` and `secondSecret`. **They are shown only in this response**: store both in your secret manager straight away. An idempotent replay of the request returns the endpoint with the secrets set to `null` (header `Idempotent-Replay-Redacted: true`); if you lost the original response, rotate both secrets. Rules for an endpoint: * `eventTypes` holds 1 to 30 unique types that your partner may subscribe to; any other type fails with `400` [`data-failed-validation`](/errors/catalog#data-failed-validation) and the constraint [`webhook-event-type-not-allowed`](/errors/catalog#webhook-event-type-not-allowed). [List webhook event types](/api-reference/partner-webhook-event-types-list) returns the types you can use. * Billing types (`invoice-*`, `payment-failed`, `payment-method-deactivated`) need `view-consumer-billing`, and `price-terms-*` types need `view-consumer-pricing`, held by the user or API key that creates or changes the endpoint. Without it, every write on such an endpoint answers `403` [`missing-permission`](/errors/catalog#missing-permission). * A partner has at most 10 endpoints ([`webhook-endpoint-limit-reached`](/errors/catalog#webhook-endpoint-limit-reached)). * [Update](/api-reference/partner-webhook-endpoints-update) replaces `eventTypes` as a whole. [Pause](/api-reference/partner-webhook-endpoints-pause) and [resume](/api-reference/partner-webhook-endpoints-resume) stop and restart deliveries; events that occur while an endpoint is paused are logged as not delivered and can be replayed within 30 days of the event (resume the endpoint first). [Delete](/api-reference/partner-webhook-endpoints-delete) is final. * Owners and your technical contact receive an email when an endpoint is created or its URL or event types change. ## Endpoint requirements * **HTTPS only**, on port 443 or 8443, with a certificate that validates. Plain HTTP is refused. * **Publicly resolvable.** URLs that resolve to private, loopback, link-local or cloud metadata addresses are refused when you save them (`422` [`webhook-url-not-allowed`](/errors/catalog#webhook-url-not-allowed)) and again at send time. * **Answer `2xx` within 15 seconds.** Acknowledge first, then do the work asynchronously (for example through your own queue). Any other status, a timeout or a connection error counts as a failure. * **No redirects.** AllRide does not follow `3xx` responses; a redirect counts as a failure. * **Allow AllRide's egress addresses** if your firewall filters inbound traffic. They are published in [Network and IP addresses](/guides/network). * **Verify every request** with the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Each request carries these headers: | Header | Value | | - | - | | `content-type` | `application/json` | | `user-agent` | `AllRide-Voucher-Webhooks/1.0 (+https://docs-provider.voucher.allride.io/webhooks)` | | `x-signing-signature` | `t=,s1=,s2=` | ## Payload format Every body is a JSON object of at most 64 KiB with two properties, `event` and `payload` (the format of AllRide's company webhooks): | Property | Meaning | | - | - | | `event.id` | The event id, a UUID. It is the same on every attempt and replay of the event: deduplicate on it. | | `event.type` | The event type in lower-case kebab case, for example `redemption-created`. | | `event.timestamp` | When AllRide created the event, in Unix seconds (UTC). It does not change between attempts; the signing time is `t` in `x-signing-signature`. | | `payload` | The resource the event is about. It starts with `resource` and `id`, followed by the resource's fields as the Consumer API represents it for you (with `_links`), or the fields the event type defines. | ```json theme={null} { "event": { "id": "3f1c9a52-7b4e-4d21-9c6a-0e5d8b2f4a17", "type": "redemption-voided", "timestamp": 1793692800 }, "payload": { "resource": "redemption", "id": 6017, "status": "voided", "voucherSetId": 2030, "redeemedAt": "2026-10-21T17:42:10.120Z", "voidedAt": "2026-11-03T08:00:00Z", "voidReason": "Customer cancelled the order", "_links": { "self": { "href": "https://api.voucher.allride.io/consumer/v1/redemptions/6017", "type": "application/json" } } } } ``` The example is shortened; each type's page in the [event catalog](/webhooks/events) shows its full payload. `payload` is a snapshot taken when the event was created. To act on the latest state, fetch `_links.self`. Ignore properties and event types you do not know: new ones can be added without a new API version. A partner that is both a provider and a consumer receives the consumer view of its own redemptions here; the provider site documents the provider view. > [!WARNING] > > Consumer `redemption-created` and `redemption-voided` payloads contain the issued voucher code or link and your `metadata`. Treat them like the API responses they mirror: store them securely and show issued values only to the entitled customer. ## Delivery guarantees * **At least once.** An event can arrive more than once, for example after a timeout or a replay. Make handlers idempotent: deduplicate on `event.id`. * **Unordered, possibly late.** Events can arrive in any order: retries arrive later than newer events, and events are stored before they are sent, so they are never silently dropped: if a send is delayed, an automatic sweep re-queues a delivery never attempted after about 10 minutes, or last attempted more than 30 minutes ago, so it may arrive late. A delivery that still cannot be sent after 3 re-queues becomes `FAILED` with `delivery-stalled` and can be replayed. Use `event.timestamp` (or the resource's own timestamps) to drop stale updates, and re-fetch the resource when the order matters. * **Stored, retried, replayable.** Each event is stored before it is sent. A failed attempt is sent again with backoff (about 30 seconds growing to 10 minutes), up to 5 attempts in total; then the delivery is `FAILED` and no longer retried, but you can [replay it](/webhooks/retries-and-logs#replay) while it is in the delivery log, 30 days after the event. A replay re-sends the same stored event with the same `event.id` and five new attempts; the log keeps counting (`attemptNo`, `attemptCount`, `replayCount`). * **Signed at each send.** The body is stored at event time and signed when it is sent, so every attempt and replay carries a fresh `t` and fresh signatures while the body (including `event.id` and `event.timestamp`) stays byte for byte the same. ## Test events [Send a test event](/api-reference/partner-webhook-endpoints-send-test-event) delivers a [`webhook-endpoint-test`](/webhooks/events/webhook-endpoint-test) event to one endpoint, even if the endpoint does not subscribe to that type. Use it to check reachability and your signature verification. ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints/11069:send-test-event" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "message": "Hello from the integration test" }' ``` The API answers `202` with the `webhookDeliveryId` of the stored test event, which is `PENDING` in the log until it is `DELIVERED` or `FAILED`. The body your endpoint receives looks like this: ```json theme={null} { "event": { "id": "0c5b7e2d-91a4-4f38-b6d2-7e3a1f9c0b54", "type": "webhook-endpoint-test", "timestamp": 1793610900 }, "payload": { "resource": "webhook-endpoint", "id": 11069, "message": "Hello from the integration test" } } ``` Test events need an `active` endpoint (a paused or deleted one answers `409` [`webhook-endpoint-not-active`](/errors/catalog#webhook-endpoint-not-active)) and active API access. They are retried like any other event. The sandbox is a separate environment: register a sandbox endpoint there with sandbox API keys. ## Related * [Verifying signatures](/webhooks/verify-signatures) * [Retries, logs and replay](/webhooks/retries-and-logs) * [Event catalog](/webhooks/events) * [Network and IP addresses](/guides/network) * [Idempotency](/guides/idempotency) # Retries, logs and replay Source: https://allride-voucher-consumer.mintlify.app/webhooks/retries-and-logs How failed webhook deliveries are retried, how to read the delivery log, and how to replay events. A **delivery attempt** is one HTTP request to your endpoint. An attempt succeeds when your endpoint answers with any `2xx` status within 15 seconds. Everything else is a failure: a `3xx` (redirects are not followed), `4xx` or `5xx` status, a timeout, a TLS, DNS or connection error, or a URL that resolves to an address AllRide refuses to call. ## Retry schedule When an event occurs, AllRide first **stores** it, with its complete body, for every endpoint that subscribes to its type, in the same transaction as the change that caused it. The delivery shows up in the log at once as `PENDING`. Only then is the send triggered through AllRide's delivery queue (Google Cloud Pub/Sub). The sender acknowledges the queue message only when your endpoint answered `2xx` (the delivery becomes `DELIVERED`); after a failed attempt the message is not acknowledged and the queue delivers it again: * with an increasing backoff, starting at about **30 seconds** and growing to at most **10 minutes** between attempts; * at most **5 delivery attempts** in total. When the fifth attempt fails the delivery becomes `FAILED` and is no longer retried; you can still [replay](#replay) it while it is in the delivery log, 30 days after the event; * with the body stored at event time, byte for byte (same `event.id` and `event.timestamp`), signed again at each send: `t` in `x-signing-signature` and the signatures differ between attempts while the body stays identical; * `Retry-After` headers in your responses are not honoured; the backoff is fixed by the platform. **Stored first, so never silently dropped.** If triggering a send fails or is lost, an automatic sweep re-queues the delivery: a delivery that was never attempted after about **10 minutes**, and a delivery whose last attempt was more than **30 minutes** ago and that is still `PENDING`. A delivery that still cannot be sent after **3 re-queues** becomes `FAILED` with `lastErrorCode` `delivery-stalled` (a fault on AllRide's side, which AllRide monitors), and you can [replay](#replay) it. Deliveries can therefore arrive late and out of order: do not rely on the order of deliveries: deduplicate on `event.id` and use `event.timestamp` or the resource's own timestamps to discard stale updates. > [!TIP] > > Acknowledge with `204` as soon as the signature checks out and queue the work on your side. A receiver that does slow work before it answers turns a busy period into timeouts and retries. ## Automatic disabling AllRide **never disables an endpoint automatically**, however many deliveries fail. Deliveries stop only when: * you [pause](/api-reference/partner-webhook-endpoints-pause) or [delete](/api-reference/partner-webhook-endpoints-delete) the endpoint. While an endpoint is paused, its events are stored as `FAILED` deliveries with `lastErrorCode` `endpoint-disabled` and no attempt, and can be replayed within 30 days of the event ([resume](/api-reference/partner-webhook-endpoints-resume) the endpoint first); * your partner is not `active`, or its API access is not `active`. Events in such a period are neither delivered nor logged and cannot be replayed. Each endpoint shows the reason in `deliveriesBlockedReason` (`api-access-inactive` or `partner-inactive`, else `null`). Watch the delivery log (`filter[status]=FAILED`) or alert on your own receiver's error rate: a delivery that fails 5 times stays `FAILED` until you replay it. ## Delivery logs The delivery log has one **delivery** per stored event and endpoint, kept for **30 days**. Read it in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/webhook-deliveries) or with [List deliveries](/api-reference/partner-webhook-deliveries-list) and [Get a delivery](/api-reference/partner-webhook-deliveries-get) (permission `view-webhook-deliveries`, delegable to API keys). A delivery is listed as soon as its event is stored, before the first send: | `status` | Meaning | | - | - | | `PENDING` | Stored and queued, or waiting for the next retry. Also the state after you replay a delivery. | | `DELIVERED` | Your endpoint answered `2xx`; `deliveredAt` says when. | | `FAILED` | The last of the 5 attempts failed, or no request could be sent (see [`lastErrorCode`](#why-a-delivery-failed-lasterrorcode)). Replay it to send it again. | | Field | Meaning | | - | - | | `webhookEventId`, `eventType` | `event.id` and `event.type` of the body. Identical on every attempt and replay. | | `webhookEndpointId`, `url` | The endpoint and the URL of the latest attempt. | | `status`, `lastStatusCode`, `lastErrorCode`, `deliveredAt` | Where the delivery stands. | | `attemptCount`, `replayCount` | The total number of attempts (the length of `attempts[]`, across replays) and how often you replayed the delivery. | | `payload` | The body as stored at event time, which every attempt sends unchanged, with sensitive values masked (see below). | | `attempts[]` | One entry per HTTP attempt, oldest first: `attemptNo` (1, 2, …; never restarted by a replay), `trigger` (`event`, or `replay` for a send you requested), `signature` (the `x-signing-signature` value of that attempt; secrets are never logged), `statusCode` (`null` without an HTTP response), `responseBody` (the first 4 KiB of your response; your content, so display it as text, never as HTML), `errorCode` (see the [table below](#why-a-delivery-failed-lasterrorcode)), `durationMs` and `attemptedAt`. | | `createdAt`, `updatedAt` | When the event was stored and when the delivery last changed. | ### Why a delivery failed (`lastErrorCode`) `lastErrorCode` is the `errorCode` of the last attempt, or one of the delivery-level codes below when no request was sent for it. Every `FAILED` delivery can be [replayed](#replay) while it is in the log. | `lastErrorCode` | Level | Meaning | | - | - | - | | `timeout` | attempt | No complete response within 15 seconds | | `connection-refused` | attempt | The connection was refused or reset | | `dns-error` | attempt | Your host name did not resolve | | `tls-error` | attempt | The TLS handshake or certificate check failed | | `ssrf-blocked` | attempt | Your host resolved to an address AllRide does not call (private or internal ranges) | | `http-4xx` | attempt | Your endpoint answered `4xx` (including `410` and `429`) | | `http-5xx` | attempt | Your endpoint answered `5xx` | | `redirect-not-followed` | attempt | Your endpoint answered `3xx`; redirects are never followed | | `internal-error` | attempt | A fault on AllRide's side while sending; the attempt is retried | | `endpoint-disabled` | delivery | The endpoint was paused or deleted when the event was stored or sent | | `partner-inactive` | delivery | Your partner or its API access was not `active` when the delivery was due | | `endpoint-unsubscribed` | delivery | The endpoint no longer subscribes to the event type | | `subject-erased` | delivery | The redemption entity the event describes was erased, so the payload is gone | | `delivery-stalled` | delivery | The delivery could not be sent after 3 automatic re-queues (a fault on AllRide's side) | | `attempt-lost` | delivery | The fifth attempt was started but its result was never recorded (AllRide's sender stopped during it); your endpoint may have received it | Filter the list by `webhookEndpointId`, `status`, `eventType`, `lastStatusCode` (`gte`, `lte`), `webhookEventId` and `createdAt`; the default order is newest first. ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/partner/v1/webhook-deliveries" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "filter[webhookEndpointId]=11069" \ --data-urlencode "filter[status]=FAILED" \ --data-urlencode "sort=-createdAt" ``` **Masking.** Your endpoint receives the real body. The log stores a masked copy, because the log permission is held by roles that may not see voucher values or customer data: * `issued.code` and `issued.link` of `redemption-created` and `redemption-voided` are masked; * a redemption entity's `email` becomes the first character of the local part, `••••` and the domain (`a••••@example.com`); its `refId` and `displayName` become `"••••"`. **Billing and price events.** Deliveries of billing types are visible only to principals that also hold `view-consumer-billing`, and deliveries of `price-terms-*` types only to principals with `view-consumer-pricing`. For others they are left out of lists, and reading or replaying one answers `404` [`webhook-delivery-not-found`](/errors/catalog#webhook-delivery-not-found). **Erased entities.** After you [erase a redemption entity](/consumer/redemption-entities), the events that concern it have no payload any more: their deliveries show `payload: null` and they cannot be replayed. To keep the log as a file, page through [List webhook deliveries](/api-reference/partner-webhook-deliveries-list) as described in [Downloading large lists](/guides/pagination#downloading-large-lists); the rows hold the same masked bodies. ## Replay Replays re-send an event that your endpoint missed, for example after an outage. They need `manage-webhooks` (delegable to API keys), an `active` endpoint (else `409` [`webhook-endpoint-not-active`](/errors/catalog#webhook-endpoint-not-active)) and active API access (else `403` [`api-access-inactive`](/errors/catalog#api-access-inactive)). A delivery can be replayed, singly or in bulk, while it is in the delivery log: **30 days** after its event was created. After that the delivery is gone (`404` [`webhook-delivery-not-found`](/errors/catalog#webhook-delivery-not-found)). An event whose payload was removed because its entity was erased answers `410` [`webhook-event-expired`](/errors/catalog#webhook-event-expired). A replay ignores the endpoint's current subscription: you asked for it explicitly. **One delivery.** [Replay a delivery](/api-reference/partner-webhook-deliveries-replay) re-sends the same stored event: the same body and `event.id`, with a fresh `t` and signatures. It answers `202` with the `webhookDeliveryId`; the delivery goes back to `PENDING`, its new attempts are added with `trigger: replay`, and it is retried like any other delivery. A replay starts **five new attempts**. The log keeps counting: `attemptNo` continues after the earlier attempts (a `FAILED` delivery with five attempts that is replayed and then answered `2xx` shows `attemptNo` 1 to 6), `attemptCount` is the total number of attempts (the length of `attempts[]`), and `replayCount` says how often the delivery was replayed. ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-deliveries/21069:replay" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` **All failed events of a time window.** [Replay failed deliveries](/api-reference/partner-webhook-endpoints-replay-failed) re-sends, oldest first, every stored event of one endpoint in `[since, until)` whose delivery is `FAILED` (all attempts failed, or stored while the endpoint was paused), optionally only for some `eventTypes`. Each one goes back to `PENDING` with its original `event.id`. ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints/11069:replay-failed" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "since": "2026-11-01T00:00:00Z", "until": "2026-11-02T00:00:00Z", "eventTypes": ["redemption-created"] }' ``` ```json theme={null} { "data": { "resource": "webhook-replay-result", "replayedCount": 37, "truncated": false, "nextSince": null, "_links": { "deliveries": { "href": "https://api.voucher.allride.io/partner/v1/webhook-deliveries?filter%5BwebhookEndpointId%5D=11069&filter%5Bstatus%5D=PENDING", "type": "application/json" } } } } ``` * `since` may be at most 30 days in the past (else `400` [`data-failed-validation`](/errors/catalog#data-failed-validation) with the constraint [`out-of-range`](/errors/catalog#out-of-range)); `until` defaults to now. * One call queues at most **10,000** events. When more match, `truncated` is `true`: call again with `since` set to `nextSince` once the first batch has gone out. * The events are sent in the background; follow them in the log with `filter[status]=PENDING`. While a bulk replay of the same endpoint is still running, a second call answers `409` [`invalid-state-transition`](/errors/catalog#invalid-state-transition). Pausing the endpoint stops the remaining sends. * Billing and `price-terms-*` events are included only when you hold the matching read permission; events of erased entities are skipped and not counted. Because a replay delivers an event again, your handler must be idempotent (see [Delivery guarantees](/webhooks/overview#delivery-guarantees)). ## Related * [Webhooks](/webhooks/overview) * [Verifying signatures](/webhooks/verify-signatures) * [Event catalog](/webhooks/events) * [Pagination, filtering and sorting](/guides/pagination) # Verifying signatures Source: https://allride-voucher-consumer.mintlify.app/webhooks/verify-signatures Check the x-signing-signature header of every webhook with HMAC-SHA256 and your two endpoint secrets. Every webhook request is signed, so you can prove that it comes from AllRide and that the body was not changed on the way. Verify the signature on every request **before** you parse or act on the body, and reject requests that fail with `400`. ## Signature scheme Each request carries one header: ```text theme={null} x-signing-signature: t=,s1=,s2= ``` * `t` is the time of signing in **Unix seconds** (UTC). * Each webhook endpoint has **two secrets**, `firstSecret` and `secondSecret`. `s1` is computed with the first secret and `s2` with the second. * Each signature is the **lowercase hexadecimal HMAC-SHA256** of the string `.`: the exact request body bytes as received (before any JSON parsing or re-serialisation), a dot, then the value of `t`. The key is the secret's UTF-8 bytes. A request is authentic when **either** `s1` or `s2` matches the HMAC computed with **either** of the secrets you have stored. Compare in constant time. Protect against replayed requests by rejecting a `t` that is too far from your clock. We recommend a tolerance of **5 minutes**. Replays that you start from the delivery log are signed again with a fresh `t`, so they pass this check. Retries and replays keep the body's `event.id`, so deduplicate on it after the signature check. Both secrets are shown only once: when you [create the endpoint](/api-reference/partner-webhook-endpoints-create) (both) and when you rotate one of them (the new one). AllRide never shows them again, not in the dashboard and not in the delivery log, which records only the header value that was sent. ## Verify with a library AllRide does not publish an SDK or a verification library. Every mainstream language ships HMAC-SHA256 and a constant-time comparison in its standard library, so the verification is about 20 lines; the handlers below need nothing else. If your web framework parses JSON before your code runs, configure the webhook route to give you the **raw body** (for example `express.raw({ type: "application/json" })` in Express, `request.get_data()` in Flask, `receiveText()` in Ktor): a body that was parsed and serialised again no longer matches the signature. ## Verify manually Each handler below reads the raw body, verifies `s1` or `s2` against the secrets in the environment variables `WEBHOOK_FIRST_SECRET` and `WEBHOOK_SECOND_SECRET` (during a rotation one of them may be unset), answers `204` on success and `400` on failure. Process the event after you have acknowledged it. ```typescript Node.js (TypeScript) theme={null} import { createHmac, timingSafeEqual } from "node:crypto"; import { createServer } from "node:http"; const SECRETS = [process.env.WEBHOOK_FIRST_SECRET, process.env.WEBHOOK_SECOND_SECRET].filter((s): s is string => Boolean(s)); const TOLERANCE_SECONDS = 300; export function verify(rawBody: Buffer, header: string | undefined, secrets: string[] = SECRETS, now = Math.floor(Date.now() / 1000)) { const parts: Record = Object.fromEntries( (header ?? "").split(",").map((p) => { const i = p.indexOf("="); return [p.slice(0, i).trim(), p.slice(i + 1).trim()]; }), ); const t = Number(parts.t); if (!Number.isInteger(t) || Math.abs(now - t) > TOLERANCE_SECONDS) return false; const received = [parts.s1, parts.s2].filter((s): s is string => /^[0-9a-f]{64}$/.test(s ?? "")); for (const secret of secrets) { const expected = createHmac("sha256", secret).update(Buffer.concat([rawBody, Buffer.from(`.${t}`)])).digest(); for (const s of received) if (timingSafeEqual(expected, Buffer.from(s, "hex"))) return true; } return false; } createServer((req, res) => { const chunks: Buffer[] = []; req.on("data", (c) => chunks.push(c)); req.on("end", () => { const rawBody = Buffer.concat(chunks); // the exact bytes, before any JSON parsing if (!verify(rawBody, req.headers["x-signing-signature"] as string | undefined)) { res.writeHead(400).end(); return; } const event = JSON.parse(rawBody.toString("utf8")); res.writeHead(204).end(); // acknowledge first, then process asynchronously setImmediate(() => console.log("received", event.event.type, event.event.id)); }); }).listen(Number(process.env.PORT ?? 8080)); ``` ```python Python theme={null} import hashlib import hmac import json import os import time from http.server import BaseHTTPRequestHandler, HTTPServer SECRETS = [s for s in (os.environ.get("WEBHOOK_FIRST_SECRET"), os.environ.get("WEBHOOK_SECOND_SECRET")) if s] TOLERANCE_SECONDS = 300 def verify(raw_body: bytes, header: str | None, secrets=SECRETS, now: int | None = None) -> bool: parts = dict(p.strip().split("=", 1) for p in (header or "").split(",") if "=" in p) try: t = int(parts.get("t", "")) except ValueError: return False now = int(time.time()) if now is None else now if abs(now - t) > TOLERANCE_SECONDS: return False received = [parts[k] for k in ("s1", "s2") if k in parts] for secret in secrets: expected = hmac.new(secret.encode(), raw_body + f".{t}".encode(), hashlib.sha256).hexdigest() if any(hmac.compare_digest(expected, s) for s in received): return True return False class Handler(BaseHTTPRequestHandler): def do_POST(self): raw_body = self.rfile.read(int(self.headers.get("Content-Length", 0))) # exact bytes if not verify(raw_body, self.headers.get("x-signing-signature")): self.send_response(400) self.end_headers() return event = json.loads(raw_body) self.send_response(204) # acknowledge first, then process asynchronously self.end_headers() print("received", event["event"]["type"], event["event"]["id"]) if __name__ == "__main__": HTTPServer(("", int(os.environ.get("PORT", "8080"))), Handler).serve_forever() ``` ```kotlin Kotlin theme={null} import com.sun.net.httpserver.HttpServer import java.net.InetSocketAddress import java.security.MessageDigest import java.util.HexFormat import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import kotlin.math.abs const val TOLERANCE_SECONDS = 300L fun verify(rawBody: ByteArray, header: String?, secrets: List, now: Long = System.currentTimeMillis() / 1000): Boolean { val parts = (header ?: "").split(",").mapNotNull { p -> p.split("=", limit = 2).takeIf { it.size == 2 }?.let { it[0].trim() to it[1].trim() } }.toMap() val t = parts["t"]?.toLongOrNull() ?: return false if (abs(now - t) > TOLERANCE_SECONDS) return false val received = listOfNotNull(parts["s1"], parts["s2"]).filter { it.matches(Regex("[0-9a-f]{64}")) } return secrets.any { secret -> val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256")) } mac.update(rawBody) val expected = mac.doFinal(".${parts["t"]}".toByteArray(Charsets.UTF_8)) received.any { MessageDigest.isEqual(expected, HexFormat.of().parseHex(it)) } } } fun main() { val secrets = listOfNotNull(System.getenv("WEBHOOK_FIRST_SECRET"), System.getenv("WEBHOOK_SECOND_SECRET")) val server = HttpServer.create(InetSocketAddress(8080), 0) server.createContext("/") { exchange -> val rawBody = exchange.requestBody.readAllBytes() // the exact bytes, before any JSON parsing val ok = verify(rawBody, exchange.requestHeaders.getFirst("x-signing-signature"), secrets) exchange.sendResponseHeaders(if (ok) 204 else 400, -1) // acknowledge, then process asynchronously exchange.close() } server.start() } ``` ```java Java theme={null} import com.sun.net.httpserver.HttpServer; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.HashMap; import java.util.HexFormat; import java.util.List; import java.util.Map; import java.util.Objects; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class WebhookReceiver { static final long TOLERANCE_SECONDS = 300; static boolean verify(byte[] rawBody, String header, List secrets, long now) throws Exception { Map parts = new HashMap<>(); for (String p : (header == null ? "" : header).split(",")) { int i = p.indexOf('='); if (i > 0) parts.put(p.substring(0, i).trim(), p.substring(i + 1).trim()); } long t; try { t = Long.parseLong(parts.getOrDefault("t", "")); } catch (NumberFormatException e) { return false; } if (Math.abs(now - t) > TOLERANCE_SECONDS) return false; byte[] suffix = ("." + parts.get("t")).getBytes(StandardCharsets.UTF_8); for (String secret : secrets) { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); mac.update(rawBody); byte[] expected = mac.doFinal(suffix); for (String key : List.of("s1", "s2")) { String s = parts.get(key); if (s == null || !s.matches("[0-9a-f]{64}")) continue; if (MessageDigest.isEqual(expected, HexFormat.of().parseHex(s))) return true; } } return false; } public static void main(String[] args) throws Exception { List secrets = java.util.stream.Stream.of(System.getenv("WEBHOOK_FIRST_SECRET"), System.getenv("WEBHOOK_SECOND_SECRET")) .filter(Objects::nonNull).toList(); HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/", exchange -> { byte[] rawBody = exchange.getRequestBody().readAllBytes(); // the exact bytes boolean ok; try { ok = verify(rawBody, exchange.getRequestHeaders().getFirst("x-signing-signature"), secrets, System.currentTimeMillis() / 1000); } catch (Exception e) { ok = false; } exchange.sendResponseHeaders(ok ? 204 : 400, -1); // acknowledge, then process asynchronously exchange.close(); }); server.start(); } } ``` ```go Go theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "io" "net/http" "os" "strconv" "strings" "time" ) const toleranceSeconds = 300 func verify(rawBody []byte, header string, secrets []string, now int64) bool { parts := map[string]string{} for _, p := range strings.Split(header, ",") { if k, v, ok := strings.Cut(strings.TrimSpace(p), "="); ok { parts[k] = v } } t, err := strconv.ParseInt(parts["t"], 10, 64) if err != nil || now-t > toleranceSeconds || t-now > toleranceSeconds { return false } for _, secret := range secrets { if secret == "" { continue } mac := hmac.New(sha256.New, []byte(secret)) mac.Write(rawBody) mac.Write([]byte("." + parts["t"])) expected := mac.Sum(nil) for _, s := range []string{parts["s1"], parts["s2"]} { if got, err := hex.DecodeString(s); err == nil && hmac.Equal(expected, got) { return true } } } return false } func main() { secrets := []string{os.Getenv("WEBHOOK_FIRST_SECRET"), os.Getenv("WEBHOOK_SECOND_SECRET")} http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { rawBody, err := io.ReadAll(r.Body) // the exact bytes, before any JSON parsing if err != nil || !verify(rawBody, r.Header.Get("x-signing-signature"), secrets, time.Now().Unix()) { w.WriteHeader(http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) // acknowledge, then process rawBody asynchronously }) http.ListenAndServe(":8080", nil) } ``` ```php PHP theme={null} TOLERANCE_SECONDS) { return false; } foreach ($secrets as $secret) { $expected = hash_hmac('sha256', $rawBody . '.' . $parts['t'], $secret); foreach (['s1', 's2'] as $key) { if (isset($parts[$key]) && hash_equals($expected, $parts[$key])) { return true; } } } return false; } $secrets = array_values(array_filter([getenv('WEBHOOK_FIRST_SECRET'), getenv('WEBHOOK_SECOND_SECRET')])); $rawBody = file_get_contents('php://input'); // the exact bytes, before json_decode if (!verify_signature($rawBody, $_SERVER['HTTP_X_SIGNING_SIGNATURE'] ?? null, $secrets)) { http_response_code(400); exit; } $event = json_decode($rawBody, true); http_response_code(204); // acknowledge, then hand $event to your queue ``` To compute a signature by hand, for example while debugging, use `openssl` on a file that holds the exact body: ```bash theme={null} T=1793610900 # the t value of the header { cat body.json; printf '.%s' "$T"; } | openssl dgst -sha256 -hmac "$WEBHOOK_FIRST_SECRET" | awk '{print $NF}' ``` The output must equal `s1` (and the same command with `WEBHOOK_SECOND_SECRET` must equal `s2`). Make sure the file has no trailing newline that was not in the request. ## Test vector Use these documentation values to test your implementation. They are not real secrets. | Input | Value | | - | - | | First secret | `doc-first-secret-0123456789abcdef` | | Second secret | `doc-second-secret-0123456789abcde` | | `t` | `1793610900` (2026-11-02T09:15:00Z) | Raw body (1476 bytes, UTF-8, no trailing newline; the two `€` signs are multi-byte characters): ```text theme={null} {"event":{"id":"3f1c9a52-7b4e-4d21-9c6a-0e5d8b2f4a17","type":"redemption-created","timestamp":1793610900},"payload":{"resource":"redemption","id":6017,"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:48213","_links":{"self":{"href":"https://api.voucher.allride.io/consumer/v1/redemption-entities/9017","type":"application/json"}}},"redemptionLink":null,"batchId":null,"metadata":{"redemptionReason":"standard","forMonth":"2026-10"},"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"}}}} ``` Expected header: ```text theme={null} x-signing-signature: t=1793610900,s1=698ac9c68e40a2f0a3bfa20d395821aad080e36ba222d328ed95bbba1d7acbd4,s2=d437486353077d2fd731197c10a2ea2c5279a4274eff0b81beff3ddbd92331f4 ``` A correct implementation: * accepts the request with both secrets configured; * accepts it with only the second secret configured (the situation during a rotation of the first secret); * rejects it when one byte of the body changes; * rejects it when your clock is more than 5 minutes away from `t` (set "now" to `1793610900` when you run the vector). Check your handler against this vector before you go live: it must accept the request, and reject it when you change one byte of the body. ## Secret rotation The two secrets let you rotate without dropping a single webhook, because AllRide signs every request with both: **1. Rotate the first secret** Call [Rotate the first secret](/api-reference/partner-webhook-endpoints-rotate-first-secret) (or use the developer dashboard). The response shows the new `firstSecret` once. From now on, `s1` uses the new secret and `s2` still uses the unchanged second secret, so your receiver keeps accepting requests through `s2`. **2. Update your receiver** Store the new first secret and deploy. Your receiver now accepts `s1` again. **3. Rotate the second secret** Call [Rotate the second secret](/api-reference/partner-webhook-endpoints-rotate-second-secret), store the new `secondSecret` and deploy again. Rotation needs `manage-webhooks` (and a recent sign-in for dashboard users). If a rotation response gets lost, a retry with the same `Idempotency-Key` returns the endpoint with the secret set to `null`; rotate that slot again. Rotate immediately if you suspect a secret has leaked. ## Related * [Webhooks](/webhooks/overview) * [Retries, logs and replay](/webhooks/retries-and-logs) * [Event catalog](/webhooks/events) * [Rotate the first secret](/api-reference/partner-webhook-endpoints-rotate-first-secret) # Audit log Source: https://allride-voucher-consumer.mintlify.app/workspace/audit-log See who changed what in your partner, through the dashboard or the API, and when. The audit log records every change made in your partner: by your members in the dashboards, by your API keys and by AllRide staff. Use it to answer "who turned off this voucher set?" or "which key created these links?", and as evidence for your own compliance checks. ## What is recorded Every state change made through the dashboards or the API is written to the audit log in the same transaction as the change itself, for example: * catalog settings (provider and voucher set toggles, positions, references), redemption entities (create, update, block, unblock, erase), Redemption Links, voids of redemptions; * email OTP settings, the SMTP configuration and email templates; * members, groups, permissions, partner settings, API keys, webhook endpoints and their secrets, the IP allowlist, custom domains, workspace branding and single sign-on; * billing account and contact changes, payment methods and Price Acceptances (reads are not recorded). Not recorded, because another record already covers them: * redemptions made with **API keys** or on landing pages, and redemption entities created inside such a redemption: the redemption itself is the record (list them with [List redemptions](/api-reference/consumer-redemptions-list)); * webhook deliveries: they have their own [delivery log](/webhooks/retries-and-logs#delivery-logs); * stock changes of voucher sets. Each entry shows the changed fields as `[before, after]` pairs in `metaData`. Secrets (API key secrets, webhook secrets, the SMTP password), bearer values such as Redemption Link tokens and personal data (a redemption entity's `refId`, `displayName`, `email` and `metadata`) appear only as `••••`, so the log never keeps data you later erase. Actions by AllRide staff (for example when AllRide activates your API access) appear with the actor `{"type": "admin-user", "id": null, "label": "AllRide"}`. ## Reading the audit log Read it in the dashboards or with [List audit events](/api-reference/partner-audit-events-list) and [Get an audit event](/api-reference/partner-audit-events-get). Both need `view-audit`, which you can delegate to API keys. ```bash theme={null} curl -G "$VOUCHER_API_BASE_URL/partner/v1/audit-events" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ --data-urlencode "filter[entityType]=redemption-link" \ --data-urlencode "filter[createdAt][gte]=2026-10-01T00:00:00Z" \ --data-urlencode "filter[createdAt][lt]=2026-11-01T00:00:00Z" ``` ```json theme={null} { "data": { "data": [ { "id": 25069, "resource": "audit-event", "createdAt": "2026-10-22T07:10:02Z", "actor": { "type": "partner-user", "id": 4069, "label": "ops@example.com" }, "actionEn": "Redemption link revoked", "actionDe": "Einlöselink widerrufen", "actionType": "update", "entityType": "redemption-link", "entityId": "31069", "providerPartnerId": null, "consumerPartnerId": 1069, "metaData": { "status": ["active", "revoked"] }, "eventData": { "eventId": "b4e2d3c5-6f70-4b8c-9dae-1f203b4c5d6e", "eventType": "redemption-link-revoked" }, "_links": { "self": { "href": "https://api.voucher.allride.io/partner/v1/audit-events/25069", "type": "application/json" } } } ], "page": 1, "perPage": 25, "totalPages": 1, "total": 1 } } ``` | Field | Meaning | | - | - | | `actor` | Who made the change: a member (`partner-user`, labelled with the email), an API key (`api-key`, labelled with its name), AllRide (`admin-user`) or the platform itself (`system`). | | `actionEn`, `actionDe` | A readable description in English and German. | | `actionType`, `entityType`, `entityId` | What kind of change it was and which resource it affected. | | `consumerPartnerId`, `providerPartnerId` | Which side of your partner the change belongs to. A partner that is both provider and consumer sees both kinds of entries and can tell them apart by these fields. | | `metaData` | The changed fields as `{"field": [before, after]}`. | Filters: `actionType`, `entityType`, `entityId`, `actor.type`, `actor.id` and `createdAt` (`gt`, `gte`, `lt`, `lte`); the newest entries come first. Without `filter[createdAt][gte]` the list covers the **last 90 days**; a window longer than the retention (24 months) answers `400 data-failed-validation` with `property` `filter[createdAt]`, the constraint [`invalid-filter`](/errors/catalog#invalid-filter) and `params.maxMonths` `24`. For a complete file, page through the list as described in [Downloading large lists](/guides/pagination#downloading-large-lists). ## Retention Audit entries are kept for **24 months** and then deleted. They cannot be changed or deleted earlier, neither by you nor by AllRide. Erasing a redemption entity does not remove audit entries, which is why they never hold its personal data. ## Related * [Members and groups](/workspace/members-and-groups) * [Pagination, filtering and sorting](/guides/pagination) * [Permissions and scopes](/guides/permissions) # Custom domains Source: https://allride-voucher-consumer.mintlify.app/workspace/custom-domains Serve Redemption Link landing pages on your own host name, such as vouchers.example-transit.example.com. ## Purpose A **Custom Domain** lets your Redemption Links open on your own host name instead of AllRide's default landing host `redeem.voucher.allride.io`, so your customers see a link like `https://vouchers.example-transit.example.com/r/`. * Custom Domains serve **only Redemption Link landing pages** (`purpose` is always `redemption-landing`). The dashboards always run on AllRide host names. * Only consumer partners hold Custom Domains; a provider-only partner gets `403` [`partner-role-missing`](/errors/catalog#partner-role-missing). * You can have at most **10** domains (excluding deleted ones). * AllRide provisions and renews the TLS certificate for you. Which host a link uses: the link's own `customDomainId` if that domain is `active`, else your default landing domain (`settings.defaultLandingDomainId`) if it is `active`, else `redeem.voucher.allride.io`. The landing page's look comes from your branding (see [Landing pages and custom domains](/consumer/landing-pages)). ## Add a domain Add a domain in the [developer dashboard](https://consumer-developers.voucher.allride.io/go/custom-domains) or with [Add a custom domain](/api-reference/partner-custom-domains-create). The permission `manage-domains` can be delegated to API keys, so an integration can provision landing domains; reading needs `view-domains`. ```bash theme={null} curl -X POST "$VOUCHER_API_BASE_URL/partner/v1/custom-domains" \ -H "Authorization: Bearer $VOUCHER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "hostname": "vouchers.example-transit.example.com", "purpose": "redemption-landing" }' ``` * Use a **subdomain** with at least three labels. An apex domain such as `example-transit.example.com` answers `422` [`apex-domain-not-supported`](/errors/catalog#apex-domain-not-supported), because an apex cannot carry a CNAME record. * Host names under AllRide's own domains answer `422` [`domain-not-allowed`](/errors/catalog#domain-not-allowed). * A host name that another partner already uses actively answers `409` [`domain-taken`](/errors/catalog#domain-taken). Pending claims do not block each other: the first claim that becomes `active` wins. * More than 10 domains answer `422` [`custom-domain-limit-reached`](/errors/catalog#custom-domain-limit-reached). The response (`201`) is the domain with `status: "pending-verification"` and the DNS records to publish in `verificationRecords`. To use a domain for every link that does not name one, set it as your default with [Update security settings](/api-reference/partner-partner-update-settings) (`settings.defaultLandingDomainId`, permission `manage-settings`, dashboard only). The domain must be your own and `active` (otherwise the constraint [`custom-domain-not-usable`](/errors/catalog#custom-domain-not-usable)). To use a domain for single links, pass its id as `customDomainId` when you [create a Redemption Link](/consumer/redemption-links). ## DNS records Publish every record of `verificationRecords` at your DNS provider: | `purpose` | Type | Name | Value | | - | - | - | - | | `routing` | `CNAME` | your host name, for example `vouchers.example-transit.example.com` | `customers.voucher.allride.io` | | `ownership` | `TXT` | `_allride-voucher-domain.` | the token shown in the record | | `dcv` | as shown | as shown | as shown (certificate validation; appears a few seconds after you add the domain) | ```json theme={null} { "verificationRecords": [ { "type": "cname", "name": "vouchers.example-transit.example.com", "value": "customers.voucher.allride.io", "purpose": "routing" }, { "type": "txt", "name": "_allride-voucher-domain.vouchers.example-transit.example.com", "value": "4f0c2b1e9d8a7c6b", "purpose": "ownership" } ] } ``` Read the domain again with [Get a custom domain](/api-reference/partner-custom-domains-get) a few seconds after creating it: the ownership and certificate-validation records are added as soon as AllRide has registered the host name. Keep all records in place for as long as you use the domain; removing the CNAME takes the domain out of service. ## Status and troubleshooting | `status` | Meaning | | - | - | | `pending-verification` | Waiting for your DNS records. AllRide checks regularly, about every 10 minutes. | | `active` | Ownership verified, the CNAME points to AllRide and the certificate is active (`sslStatus`). Links on this host work. | | `failed` | Verification did not succeed within **14 days**, failed with an error, or another partner's claim for the same host became active. Read `verificationErrors`. A failed domain is deleted automatically after 7 days. | | `moved` | The domain was active, but its DNS no longer points to AllRide. It returns to `active` when you restore the records; after 7 days in `moved` it is deleted. | | `deleted` | Deleted by you or automatically. Final. | * After fixing your DNS, call [Re-check a custom domain](/api-reference/partner-custom-domains-refresh) to have it checked on the next run instead of waiting; a `failed` domain moves back to `pending-verification`. The result arrives asynchronously, so poll the domain. * Only `active` domains serve traffic. Visitors of a host that is not active see a neutral "not found" page without your data. * `lastCheckedAt` shows the last check, `activeLinkCount` how many active links use the domain, and `isDefaultLanding` whether it is your default landing domain. * DNS changes can take a while to propagate, depending on the record's TTL at your DNS provider. * [Delete a custom domain](/api-reference/partner-custom-domains-delete) when you no longer need it. Links that used it get a URL on your default landing host (or `redeem.voucher.allride.io`), and URLs you already shared on the deleted host stop working: check `activeLinkCount` first. Deleting your default landing domain clears `settings.defaultLandingDomainId`. * You receive an email when a domain becomes active, fails or is moved. ## Related * [Landing pages and custom domains](/consumer/landing-pages) * [Redemption links](/consumer/redemption-links) * [Network and IP addresses](/guides/network) # Members and groups Source: https://allride-voucher-consumer.mintlify.app/workspace/members-and-groups Invite users to your partner, organise their permissions in groups, and require multi-factor authentication or single sign-on. People sign in to the consumer dashboards as **users**. A user gets access to your partner through a **Partner Membership**, and what the membership may do is the union of the permissions of its **groups** and the permissions granted to it **directly** (its **effective permissions**). API keys do not belong to members: they carry their own scopes (see [Permissions and scopes](/guides/permissions)). One person (one email address) is one user across the whole platform and can be a member of several partners, for example of your production partner and of a sister company's partner. They choose the partner when they sign in. ## Members and invitations * **Invite** a member with [Invite a member](/api-reference/partner-members-create) or in the dashboard: give an email address and, optionally, groups (`groupIds`, at most 20) and direct permissions (`directPermissions`). The member starts as `invited` and receives an invitation email. The invitation expires after **7 days**; [resend it](/api-reference/partner-members-resend-invitation) to issue a new one (the old link stops working). * An email that already has a membership in your partner answers `409` [`membership-exists`](/errors/catalog#membership-exists). * Membership statuses: `invited` → `active` (on accepting) → `suspended` ↔ `active` → `removed`. [Suspend](/api-reference/partner-members-suspend) blocks a member temporarily; [Remove](/api-reference/partner-members-remove) is final (also for open invitations). A removed person can be invited again; that creates a new membership. * [Change a member's direct permissions](/api-reference/partner-members-update) replaces `directPermissions` as a whole. Group memberships are changed on the group. * Changes take effect within **60 seconds** on the member's next request. Guard rails that keep your partner manageable: * You can grant only permissions that you hold yourself (`403` [`missing-permission`](/errors/catalog#missing-permission)). * Nobody can change their own permissions, groups or status (`409` [`self-modification-not-allowed`](/errors/catalog#self-modification-not-allowed)). * Only members of `owners` can change the members of `owners` and `administrators` or suspend or remove an owner (`403` [`owner-membership-required`](/errors/catalog#owner-membership-required)). * The last active owner cannot be suspended or removed (`409` [`last-owner`](/errors/catalog#last-owner)). Managing members needs `manage-members` and managing groups `manage-groups`; both are **dashboard only** (not delegable to API keys), and several of these operations need a recent sign-in. Reading members (`view-members`) and groups (`view-groups`) works with API keys too. ## Groups and permissions A group has a `name`, an optional `description`, a list of `permissions` and its members (`memberIds`, at most 500). * [Create a group](/api-reference/partner-groups-create) and [update a group](/api-reference/partner-groups-update): `permissions` and `memberIds` are replaced as a whole. When two admins save the same group at the same time, the later save wins, so read the group again before you edit it. * Group names are unique among your active groups (`409` [`group-name-taken`](/errors/catalog#group-name-taken)). * [Delete a group](/api-reference/partner-groups-delete) removes its member links; a group used as the default group of single sign-on provisioning cannot be deleted (`409` [`group-in-use`](/errors/catalog#group-in-use)). * [List permissions](/api-reference/partner-permissions-list) returns the permission catalog with a label, a description, whether a permission is `delegable` to API keys and whether it is `applicable` to your partner's role. For a consumer partner every `view-provider-…` or `manage-provider-…` permission is not applicable. The full list with what each permission allows is in [Permissions and scopes](/guides/permissions). ## Default groups Every consumer partner starts with these groups. Default groups cannot be renamed or deleted; except for `owners`, you can edit their permissions. | Group | Permissions | | - | - | | `owners` | Every permission that applies to your partner, computed and immutable. | | `administrators` | Every applicable permission except `manage-sso` and `request-api-access`. | | `developers` | `view-profile`, `view-audit`, `request-api-access`, `view-api-keys`, `manage-api-keys`, `view-webhooks`, `manage-webhooks`, `view-webhook-deliveries`, `view-ip-allowlist`, `manage-ip-allowlist`, `view-domains`, plus every `view-…` permission except `view-consumer-billing` and `view-consumer-pricing`. | | `redemption-operators` | `view-profile`, `view-consumer-catalog`, `view-consumer-redemptions`, `create-consumer-redemptions`, `void-consumer-redemptions`, `view-consumer-redemption-entities`, `manage-consumer-redemption-entities`, `view-consumer-redemption-links`, `manage-consumer-redemption-links`. | | `billing` | `view-profile`, `view-consumer-billing`, `manage-consumer-billing`, `view-consumer-analytics`, `view-consumer-pricing`, `accept-consumer-pricing`. | | `viewers` | Every `view-…` permission except `view-api-keys`, `view-consumer-billing` and `view-consumer-pricing`. | `developers` and `viewers` deliberately cannot read billing data or prices. Put finance staff in `billing`. ## Multi-factor authentication and SSO * **Multi-factor authentication.** With the partner setting `mfaRequired` on, every member must use a second factor to work in your partner; members without one are asked to enrol (`403` [`mfa-required`](/errors/catalog#mfa-required) for their requests until they do). Change it with [Update security settings](/api-reference/partner-partner-update-settings) (`manage-settings`, dashboard only). Each member's `mfaEnrolled` shows whether they have a second factor. Partner administrators can never change another user's factors. * **Single sign-on.** Connect your identity provider through SAML or OIDC with the [single sign-on operations](/api-reference/partner-sso-connections-create) or in the dashboard. You list your email domains, prove ownership of each with a DNS TXT record, test the connection and then activate it. With `enforced` on, users of those domains must sign in through your identity provider (`403` [`sso-required`](/errors/catalog#sso-required) otherwise). With automatic provisioning on first sign-in (`jitProvisioning`) a first SSO sign-in creates an `active` membership in the group you choose. SSO needs `view-sso` / `manage-sso`, which are dashboard only. API keys are not affected by MFA or SSO; protect them with scopes and the [IP allowlist](/guides/ip-allowlist). ## Related * [Permissions and scopes](/guides/permissions) * [Audit log](/workspace/audit-log) * [API keys](/guides/api-keys) * [Authentication](/guides/authentication) # Remove a billing contact Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-billing-contacts-delete /openapi/consumer.v1.json delete /consumer/v1/billing/contacts/{billingContactId} Removes a billing contact. Removing the last `invoices` contact makes the account `incomplete` in the same transaction. Needs a recent sign-in. Allowed while your partner is suspended. See the [billing guide](/consumer/billing). **Required permissions:** `manage-consumer-billing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Accept Price Terms Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-pricing-acceptances-create /openapi/consumer.v1.json post /consumer/v1/pricing/acceptances Records your acceptance of the current or upcoming Price Terms periods of visible voucher sets. Dashboard users only: `accept-consumer-pricing` cannot be delegated, so API keys get 403 `missing-permission`, and AllRide cannot accept for you; a recent sign-in is required. All-or-nothing: if any item's terms changed in the meantime, the answer is 409 `price-terms-changed` and nothing is recorded. Accepting the same terms again returns `created: false`. Acceptances are immutable and appear in your audit log. See [Prices and price acceptance](/consumer/prices). **Required permissions:** `accept-consumer-pricing` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List Price Acceptances Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-pricing-acceptances-list /openapi/consumer.v1.json get /consumer/v1/pricing/acceptances Lists your Price Acceptances, including those of voucher sets that are no longer visible. See [Prices and price acceptance](/consumer/prices). **Required permissions:** `view-consumer-pricing` · **API keys:** allowed # Get the pricing summary Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-pricing-summary-get /openapi/consumer.v1.json get /consumer/v1/pricing/summary Counts the visible voucher sets that need an acceptance, have a pending change or are not priced, and returns the next change. See [Prices and price acceptance](/consumer/prices). **Required permissions:** `view-consumer-pricing` · **API keys:** allowed # Get the Price Terms of a voucher set Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-pricing-voucher-sets-get /openapi/consumer.v1.json get /consumer/v1/pricing/voucher-sets/{voucherSetId} Returns your resolved Price Terms of one visible voucher set. A set that is not visible to you answers 404 `voucher-set-not-found`. See [Prices and price acceptance](/consumer/prices). **Required permissions:** `view-consumer-pricing` · **API keys:** allowed # List Price Terms of visible voucher sets Source: https://allride-voucher-consumer.mintlify.app/api-reference/consumer-pricing-voucher-sets-list /openapi/consumer.v1.json get /consumer/v1/pricing/voucher-sets Lists your resolved Price Terms for every voucher set visible to you now: the current and up to 5 upcoming periods with their acceptance status. A visible set is redeemable only after you accept the terms in force. See [Prices and price acceptance](/consumer/prices). **Required permissions:** `view-consumer-pricing` · **API keys:** allowed # Get the API access status Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-access-get /openapi/consumer.v1.json get /partner/v1/api-access Returns the API access state of your partner. API keys work only while it is `active`; AllRide activates access after you request it. **Required permissions:** `view-profile` · **API keys:** allowed # Request API access Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-access-request /openapi/consumer.v1.json post /partner/v1/api-access:request Asks AllRide to activate API access (from `not-requested` to `requested`). AllRide reviews the request and you are notified of the decision. See [Sandbox and production](/guides/environments). **Required permissions:** `request-api-access` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Create an API key Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-keys-create /openapi/consumer.v1.json post /partner/v1/api-keys Creates a key with least-privilege scopes. The `secret` is returned once in this response; store it in a secret manager. Needs active API access; at most 20 active keys. API keys are managed only in the developer dashboard; API keys cannot call this operation. See [API keys](/guides/api-keys). **Required permissions:** `manage-api-keys` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get an API key Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-keys-get /openapi/consumer.v1.json get /partner/v1/api-keys/{apiKeyId} Returns one key without its secret. API keys are managed only in the developer dashboard; API keys cannot call this operation. See [API keys](/guides/api-keys). **Required permissions:** `view-api-keys` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List API keys Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-keys-list /openapi/consumer.v1.json get /partner/v1/api-keys Lists your API keys (metadata only). API keys are managed only in the developer dashboard; API keys cannot call this operation. See [API keys](/guides/api-keys). **Required permissions:** `view-api-keys` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Revoke an API key Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-keys-revoke /openapi/consumer.v1.json post /partner/v1/api-keys/{apiKeyId}:revoke Revokes an active key. Requests with it fail with 401 `api-key-invalid` within 60 seconds. API keys are managed only in the developer dashboard; API keys cannot call this operation. See [API keys](/guides/api-keys). **Required permissions:** `manage-api-keys` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Roll an API key Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-keys-roll /openapi/consumer.v1.json post /partner/v1/api-keys/{apiKeyId}:roll Creates a new key with the same scopes and returns its secret once; the old key keeps working until the grace period ends. Deploy the new secret within the grace period and check its `lastUsedAt`. The new key shares the old key's idempotency scope, so retries with the same `Idempotency-Key` replay instead of executing twice. API keys are managed only in the developer dashboard; API keys cannot call this operation. See [API keys](/guides/api-keys). **Required permissions:** `manage-api-keys` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Rename an API key Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-api-keys-update /openapi/consumer.v1.json patch /partner/v1/api-keys/{apiKeyId} Renames a key. Scopes are immutable: create a new key or roll instead. API keys are managed only in the developer dashboard; API keys cannot call this operation. See [API keys](/guides/api-keys). **Required permissions:** `manage-api-keys` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get an audit event Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-audit-events-get /openapi/consumer.v1.json get /partner/v1/audit-events/{auditEventId} Returns one audit event. **Required permissions:** `view-audit` · **API keys:** allowed # List audit events Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-audit-events-list /openapi/consumer.v1.json get /partner/v1/audit-events Lists the audit log of your partner. Without `filter[createdAt][gte]` the last 90 days are returned; a window may not exceed 24 months. See [Audit log](/workspace/audit-log). **Required permissions:** `view-audit` · **API keys:** allowed # Complete a brand asset upload Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-brand-assets-complete /openapi/consumer.v1.json post /partner/v1/brand-assets/{brandAssetId}:complete Confirms the upload. The image is then checked, re-encoded and published asynchronously (`processing` → `ready` or `rejected`). **Required permissions:** `manage-workspace-branding` · **API keys:** allowed # Create a brand asset Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-brand-assets-create /openapi/consumer.v1.json post /partner/v1/brand-assets Creates an asset in `pending-upload` with a signed upload URL valid for one hour. PUT the file to `uploadTarget.url` with the listed headers, then call `:complete`. **Required permissions:** `manage-workspace-branding` · **API keys:** allowed # Delete a brand asset Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-brand-assets-delete /openapi/consumer.v1.json delete /partner/v1/brand-assets/{brandAssetId} Soft-deletes an asset that is no longer referenced by branding, content or a draft or active email template. A repeated delete answers 204. **Required permissions:** `manage-workspace-branding` · **API keys:** allowed # Get a brand asset Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-brand-assets-get /openapi/consumer.v1.json get /partner/v1/brand-assets/{brandAssetId} Returns one asset; poll it after `:complete` until `ready` or `rejected`. **Required permissions:** `view-profile` · **API keys:** allowed # List brand assets Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-brand-assets-list /openapi/consumer.v1.json get /partner/v1/brand-assets Lists your brand assets. Deleted assets appear only when you filter by `status`. **Required permissions:** `view-profile` · **API keys:** allowed # Add a custom domain Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-custom-domains-create /openapi/consumer.v1.json post /partner/v1/custom-domains Adds a host name in `pending-verification`. Publish the CNAME to `customers.voucher.allride.io` and the TXT ownership and certificate records that appear on the domain shortly after (poll it). Consumer partners only. See [Custom domains](/workspace/custom-domains). **Required permissions:** `manage-domains` · **API keys:** allowed # Delete a custom domain Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-custom-domains-delete /openapi/consumer.v1.json delete /partner/v1/custom-domains/{customDomainId} Deletes the domain (terminal). Links on it fall back to your default landing host; if it was the default landing domain, `settings.defaultLandingDomainId` is cleared. A repeated delete answers 204. Consumer partners only. See [Custom domains](/workspace/custom-domains). **Required permissions:** `manage-domains` · **API keys:** allowed # Get a custom domain Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-custom-domains-get /openapi/consumer.v1.json get /partner/v1/custom-domains/{customDomainId} Returns one domain with its DNS records and status. Consumer partners only. See [Custom domains](/workspace/custom-domains). **Required permissions:** `view-domains` · **API keys:** allowed # List custom domains Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-custom-domains-list /openapi/consumer.v1.json get /partner/v1/custom-domains Lists your custom domains. Consumer partners only. See [Custom domains](/workspace/custom-domains). **Required permissions:** `view-domains` · **API keys:** allowed # Re-check a custom domain Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-custom-domains-refresh /openapi/consumer.v1.json post /partner/v1/custom-domains/{customDomainId}:refresh Schedules an immediate re-check of the DNS records (a `failed` domain returns to `pending-verification`). Poll the domain for the result. Consumer partners only. See [Custom domains](/workspace/custom-domains). **Required permissions:** `manage-domains` · **API keys:** allowed # Get developer reference information Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-developer-reference-get /openapi/consumer.v1.json get /partner/v1/developer-reference Returns base URLs, the egress IP addresses AllRide connects from, the API key format, your allowlist state and webhook reference values. Billing event types are listed only when you hold `view-consumer-billing`. **Required permissions:** `view-profile` · **API keys:** allowed # Create a group Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-groups-create /openapi/consumer.v1.json post /partner/v1/groups Creates a custom group with permissions and members. **Required permissions:** `manage-groups` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Delete a group Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-groups-delete /openapi/consumer.v1.json delete /partner/v1/groups/{groupId} Deletes a custom group and its member links. A repeated delete answers 204. **Required permissions:** `manage-groups` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get a group Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-groups-get /openapi/consumer.v1.json get /partner/v1/groups/{groupId} Returns one group. **Required permissions:** `view-groups` · **API keys:** allowed # List groups Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-groups-list /openapi/consumer.v1.json get /partner/v1/groups Lists the groups of your partner. Deleted groups appear only with `filter[status]=deleted`. **Required permissions:** `view-groups` · **API keys:** allowed # List the members of a group Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-groups-members /openapi/consumer.v1.json get /partner/v1/groups/{groupId}/members Lists the memberships in one group. **Required permissions:** `view-groups`, `view-members` · **API keys:** allowed # Update a group Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-groups-update /openapi/consumer.v1.json patch /partner/v1/groups/{groupId} Changes name, description, permissions or members. This is the only way to change group membership. **Required permissions:** `manage-groups` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Add an IP allowlist entry Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-ip-allowlist-create /openapi/consumer.v1.json post /partner/v1/ip-allowlist Adds a range. Add your IPv4 and IPv6 egress ranges before you enable enforcement. Takes effect within 60 seconds. See [IP allowlist](/guides/ip-allowlist). **Required permissions:** `manage-ip-allowlist` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Remove an IP allowlist entry Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-ip-allowlist-delete /openapi/consumer.v1.json delete /partner/v1/ip-allowlist/{ipAllowlistEntryId} Removes an entry. The last entry cannot be removed while enforcement is on. See [IP allowlist](/guides/ip-allowlist). **Required permissions:** `manage-ip-allowlist` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List IP allowlist entries Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-ip-allowlist-list /openapi/consumer.v1.json get /partner/v1/ip-allowlist Lists your allowlist entries (at most 100). See [IP allowlist](/guides/ip-allowlist). **Required permissions:** `view-ip-allowlist` · **API keys:** allowed # Relabel an IP allowlist entry Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-ip-allowlist-update /openapi/consumer.v1.json patch /partner/v1/ip-allowlist/{ipAllowlistEntryId} Changes the label of an entry. See [IP allowlist](/guides/ip-allowlist). **Required permissions:** `manage-ip-allowlist` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Invite a member Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-create /openapi/consumer.v1.json post /partner/v1/members Invites a person by email. The invitation email links to the dashboard and expires after 7 days. You can grant only permissions you hold yourself. **Required permissions:** `manage-members` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get a member Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-get /openapi/consumer.v1.json get /partner/v1/members/{partnerMembershipId} Returns one membership. **Required permissions:** `view-members` · **API keys:** allowed # List members Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-list /openapi/consumer.v1.json get /partner/v1/members Lists the users of your partner and open invitations. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `view-members` · **API keys:** allowed # Remove a member or revoke an invitation Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-remove /openapi/consumer.v1.json post /partner/v1/members/{partnerMembershipId}:remove Removes the membership (terminal). The user loses access to your partner within 60 seconds; re-inviting the email creates a new membership. **Required permissions:** `manage-members` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Resend an invitation Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-resend-invitation /openapi/consumer.v1.json post /partner/v1/members/{partnerMembershipId}:resend-invitation Sends a new invitation email to an `invited` member; the previous link stops working and the new one expires after 7 days. **Required permissions:** `manage-members` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Suspend a member Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-suspend /openapi/consumer.v1.json post /partner/v1/members/{partnerMembershipId}:suspend Suspends an active member (`active` → `suspended`). **Required permissions:** `manage-members` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Unsuspend a member Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-unsuspend /openapi/consumer.v1.json post /partner/v1/members/{partnerMembershipId}:unsuspend Reactivates a suspended member (`suspended` → `active`). **Required permissions:** `manage-members` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Change a member's direct permissions Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-members-update /openapi/consumer.v1.json patch /partner/v1/members/{partnerMembershipId} Replaces the member's direct permissions. Group membership is changed with `PATCH /partner/v1/groups/{groupId}`. Takes effect within 60 seconds. **Required permissions:** `manage-members` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get your partner Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-partner-get /openapi/consumer.v1.json get /partner/v1/partner Returns your partner record with contacts, API access state and settings. Consumer partners also get `settings.emailOtpLinksEnabled` and `settings.defaultLandingDomainId`. **Required permissions:** `view-profile` · **API keys:** allowed # Update general information Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-partner-update /openapi/consumer.v1.json patch /partner/v1/partner Changes contacts, default locale or time zone (JSON Merge Patch). Changing `contacts.billingEmail` does not change your billing contacts, which you manage under `/consumer/v1/billing/contacts`. A new time zone changes how calendar months are computed from now on. **Required permissions:** `manage-settings` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Update security settings Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-partner-update-settings /openapi/consumer.v1.json patch /partner/v1/partner/settings Changes MFA policy, IP allowlist enforcement and (consumers) the default landing domain. Add IP allowlist entries before enabling enforcement. Effects reach every API instance within 60 seconds. See [IP allowlist](/guides/ip-allowlist). **Required permissions:** `manage-settings` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List permissions Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-permissions-list /openapi/consumer.v1.json get /partner/v1/permissions Lists every permission with its label, whether API keys may hold it (`delegable`) and whether it applies to your partner. `perPage=100` returns the whole catalog. See [Permissions and scopes](/guides/permissions). **Required permissions:** `view-groups` · **API keys:** allowed # Get the calling principal Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-principal-get /openapi/consumer.v1.json get /partner/v1/principal Shows which partner, environment and permissions the credential has. Call it first to check a new API key. **Required permissions:** `view-profile` · **API keys:** allowed # Activate an SSO connection Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-activate /openapi/consumer.v1.json post /partner/v1/sso-connections/{ssoConnectionId}:activate Activates a `testing` or `disabled` connection; needs at least one verified domain. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `manage-sso` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Create an SSO connection Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-create /openapi/consumer.v1.json post /partner/v1/sso-connections Creates a connection in `draft`. Publish the returned TXT records and configure `serviceProvider` in your identity provider, then call `:verify`. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `manage-sso` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Deactivate an SSO connection Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-deactivate /openapi/consumer.v1.json post /partner/v1/sso-connections/{ssoConnectionId}:deactivate Disables a `testing` or `active` connection; enforcement stops. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `manage-sso` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Get an SSO connection Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-get /openapi/consumer.v1.json get /partner/v1/sso-connections/{ssoConnectionId} Returns one connection. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `view-sso` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List SSO connections Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-list /openapi/consumer.v1.json get /partner/v1/sso-connections Lists your single sign-on connections. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `view-sso` · **API keys:** not allowed > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Update an SSO connection Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-update /openapi/consumer.v1.json patch /partner/v1/sso-connections/{ssoConnectionId} Changes the name, domains, identity provider settings, enforcement or provisioning. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `manage-sso` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # Verify SSO email domains Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-sso-connections-verify /openapi/consumer.v1.json post /partner/v1/sso-connections/{ssoConnectionId}:verify Checks the TXT records of unverified domains. With at least one verified domain a `draft` connection moves to `testing`. SSO is configured only by dashboard users. See [Members and groups](/workspace/members-and-groups). **Required permissions:** `manage-sso` · **API keys:** not allowed · **Recent sign-in:** required for dashboard users (15 minutes) > [!NOTE] > > Dashboard session only. API keys cannot call this operation. # List selectable time zones Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-timezones-list /openapi/consumer.v1.json get /partner/v1/timezones Returns every IANA zone accepted for `timezone` in [Update general information](/api-reference/partner-partner-update) and the analytics `timezone` parameter (whole-hour offsets only). Not paginated. **Required permissions:** `view-profile` · **API keys:** allowed # Get a webhook delivery Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-deliveries-get /openapi/consumer.v1.json get /partner/v1/webhook-deliveries/{webhookDeliveryId} Returns one delivery: the stored event, its status and every attempt. **Required permissions:** `view-webhook-deliveries` · **API keys:** allowed # List webhook deliveries Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-deliveries-list /openapi/consumer.v1.json get /partner/v1/webhook-deliveries Lists the stored webhook events of the last 30 days, one entry per event and endpoint, with `status` (`PENDING`, `DELIVERED`, `FAILED`) and the attempts. An event appears as `PENDING` as soon as it is stored, before the first send. Billing and `price-terms-*` deliveries are left out unless you hold `view-consumer-billing` or `view-consumer-pricing`. See [Retries, logs and replay](/webhooks/retries-and-logs). **Required permissions:** `view-webhook-deliveries` · **API keys:** allowed # Replay a webhook delivery Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-deliveries-replay /openapi/consumer.v1.json post /partner/v1/webhook-deliveries/{webhookDeliveryId}:replay Re-sends the same stored event (same body and `event.id`, fresh `t` and signatures) to an active endpoint, regardless of its current subscription. The delivery goes back to `PENDING` and its new attempts are added with `trigger: replay`. A delivery can be replayed while it is in the delivery log, which is 30 days after its event; events of erased entities cannot be replayed. **Required permissions:** `manage-webhooks` · **API keys:** allowed # Create a webhook endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-create /openapi/consumer.v1.json post /partner/v1/webhook-endpoints Registers a receiver and returns its two signing secrets once (`firstSecret`, `secondSecret`). Verify `x-signing-signature` against either. At most 10 endpoints; needs active API access. Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). See [Verifying signatures](/webhooks/verify-signatures). **Required permissions:** `manage-webhooks` · **API keys:** allowed · **Recent sign-in:** required for dashboard users (15 minutes) # Delete a webhook endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-delete /openapi/consumer.v1.json delete /partner/v1/webhook-endpoints/{webhookEndpointId} Deletes the endpoint (terminal). Its delivery log stays readable for 30 days. A repeated delete answers 204. Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed # Get a webhook endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-get /openapi/consumer.v1.json get /partner/v1/webhook-endpoints/{webhookEndpointId} Returns one endpoint without its secrets. **Required permissions:** `view-webhooks` · **API keys:** allowed # List webhook endpoints Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-list /openapi/consumer.v1.json get /partner/v1/webhook-endpoints Lists your webhook endpoints. Deleted endpoints appear only when you filter by `status`. See [Webhooks](/webhooks/overview). **Required permissions:** `view-webhooks` · **API keys:** allowed # Pause a webhook endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-pause /openapi/consumer.v1.json post /partner/v1/webhook-endpoints/{webhookEndpointId}:pause Pauses deliveries (`active` → `paused`). Events while paused are logged as `FAILED` deliveries and can be replayed within 30 days of the event (resume the endpoint first). Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed # Replay failed deliveries of an endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-replay-failed /openapi/consumer.v1.json post /partner/v1/webhook-endpoints/{webhookEndpointId}:replay-failed Re-queues every stored event of the window whose delivery to this endpoint is `FAILED`, oldest first, at most 10,000 per call, as a background task; each goes back to `PENDING` with the same `event.id`. Follow progress in the delivery log with `filter[status]=PENDING`. Billing and `price-terms-*` events are included only when you hold the matching read permission. **Required permissions:** `manage-webhooks` · **API keys:** allowed # Resume a webhook endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-resume /openapi/consumer.v1.json post /partner/v1/webhook-endpoints/{webhookEndpointId}:resume Resumes deliveries (`paused` → `active`). Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed # Rotate the first signing secret Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-rotate-first-secret /openapi/consumer.v1.json post /partner/v1/webhook-endpoints/{webhookEndpointId}:rotate-first-secret Replaces the first secret and returns it once (`firstSecret`); the second secret is unchanged, so deliveries stay verifiable while you update your receiver. Rotate one secret at a time. Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed · **Recent sign-in:** required for dashboard users (15 minutes) # Rotate the second signing secret Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-rotate-second-secret /openapi/consumer.v1.json post /partner/v1/webhook-endpoints/{webhookEndpointId}:rotate-second-secret Replaces the second secret and returns it once (`secondSecret`); the first secret is unchanged, so deliveries stay verifiable while you update your receiver. Rotate one secret at a time. Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed · **Recent sign-in:** required for dashboard users (15 minutes) # Send a test event Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-send-test-event /openapi/consumer.v1.json post /partner/v1/webhook-endpoints/{webhookEndpointId}:send-test-event Stores one `webhook-endpoint-test` event for an active endpoint, even if it is not subscribed to that type, and queues its send. The delivery appears in the log at once as `PENDING`; follow it by `webhookDeliveryId`. Normal retries apply. Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed # Update a webhook endpoint Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-endpoints-update /openapi/consumer.v1.json patch /partner/v1/webhook-endpoints/{webhookEndpointId} Changes the URL, description or event types. A URL or event type change notifies your owners and technical contact. Endpoints that include billing event types also need `view-consumer-billing`, and `price-terms-*` types `view-consumer-pricing` (403 `missing-permission` otherwise). **Required permissions:** `manage-webhooks` · **API keys:** allowed · **Recent sign-in:** required for dashboard users (15 minutes) # List webhook event types Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-webhook-event-types-list /openapi/consumer.v1.json get /partner/v1/webhook-event-types Lists the event types your partner can subscribe to (25 types for a consumer partner). `perPage=100` returns the full list. See [Event catalog](/webhooks/events). **Required permissions:** `view-webhooks` · **API keys:** allowed # Get workspace branding Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-workspace-branding-get /openapi/consumer.v1.json get /partner/v1/branding/workspace Returns your Workspace Branding, including the consumer `landing` block and a render map of the images. Any member and any API key can read it. See [Landing pages](/consumer/landing-pages). **Required permissions:** none (any member or API key) · **API keys:** allowed # Replace workspace branding Source: https://allride-voucher-consumer.mintlify.app/api-reference/partner-workspace-branding-replace /openapi/consumer.v1.json put /partner/v1/branding/workspace Replaces the complete branding content. No approval is needed, but every text and URL passes the content checks synchronously (422 `content-blocked`). Clearing `landing.privacyUrl` or `landing.imprintUrl` is refused while Redemption Links that are not revoked exist. Answers 201 on the first save and 200 afterwards; landing pages pick up changes within 60 seconds. **Required permissions:** `manage-workspace-branding` · **API keys:** allowed # catalog-voucher-set-activated Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-activated openapi/consumer.v1.json webhook catalog-voucher-set-activated A voucher set became active **Audience:** consumer · **Trigger:** The set becomes active · **Payload:** `ConsumerCatalogEntry` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-activation-scheduled Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-activation-scheduled openapi/consumer.v1.json webhook catalog-voucher-set-activation-scheduled A voucher set's activation was scheduled **Audience:** consumer · **Trigger:** The provider schedules an activation · **Payload:** `ConsumerCatalogEntry` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-available Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-available openapi/consumer.v1.json webhook catalog-voucher-set-available A voucher set became visible to you **Audience:** consumer · **Trigger:** A set becomes visible to you (activation, access granted, provider reactivated) · **Payload:** `ConsumerCatalogEntry` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-deactivated Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-deactivated openapi/consumer.v1.json webhook catalog-voucher-set-deactivated A voucher set was deactivated **Audience:** consumer · **Trigger:** The set is deactivated · **Payload:** `ConsumerCatalogEntry` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-deactivation-scheduled Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-deactivation-scheduled openapi/consumer.v1.json webhook catalog-voucher-set-deactivation-scheduled A voucher set's deactivation was scheduled **Audience:** consumer · **Trigger:** The provider schedules a deactivation · **Payload:** `ConsumerCatalogEntry` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-depleted Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-depleted openapi/consumer.v1.json webhook catalog-voucher-set-depleted A voucher set is depleted **Audience:** consumer · **Trigger:** `stockStatus` becomes `depleted` · **Payload:** `{resource, id}` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-stock-low Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-stock-low openapi/consumer.v1.json webhook catalog-voucher-set-stock-low A voucher set's stock is low **Audience:** consumer · **Trigger:** `stockStatus` becomes `low` · **Payload:** `{resource, id, stockStatus}` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-unavailable Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-unavailable openapi/consumer.v1.json webhook catalog-voucher-set-unavailable A voucher set left your catalog **Audience:** consumer · **Trigger:** The set stops being visible to you · **Payload:** `{resource, id, reason}` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # catalog-voucher-set-updated Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/catalog-voucher-set-updated openapi/consumer.v1.json webhook catalog-voucher-set-updated A visible voucher set changed **Audience:** consumer · **Trigger:** New live revision, schedule change, profile or branding change, stock available again · **Payload:** `ConsumerCatalogEntry` + `changedFields` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # invoice-finalised Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/invoice-finalised openapi/consumer.v1.json webhook invoice-finalised An invoice was issued **Audience:** consumer · **Trigger:** the invoice (also a Stornorechnung) is finalised and its document rendered · **Payload:** `Invoice` (consumer view) · **Permission:** subscribing needs `view-consumer-billing` > [!NOTE] > > Billing event. Requires a webhook endpoint subscribed by a consumer partner; payloads never include payment-provider messages. See [Billing webhooks](/consumer/billing#billing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # invoice-overdue Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/invoice-overdue openapi/consumer.v1.json webhook invoice-overdue An invoice became overdue **Audience:** consumer · **Trigger:** the due date passed with an amount due · **Payload:** `Invoice` (consumer view) · **Permission:** subscribing needs `view-consumer-billing` > [!NOTE] > > Billing event. Requires a webhook endpoint subscribed by a consumer partner; payloads never include payment-provider messages. See [Billing webhooks](/consumer/billing#billing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # invoice-paid Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/invoice-paid openapi/consumer.v1.json webhook invoice-paid An invoice was paid **Audience:** consumer · **Trigger:** the invoice is fully paid · **Payload:** `Invoice` (consumer view) · **Permission:** subscribing needs `view-consumer-billing` > [!NOTE] > > Billing event. Requires a webhook endpoint subscribed by a consumer partner; payloads never include payment-provider messages. See [Billing webhooks](/consumer/billing#billing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # invoice-voided Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/invoice-voided openapi/consumer.v1.json webhook invoice-voided An invoice was cancelled **Audience:** consumer · **Trigger:** a Stornorechnung cancels the invoice · **Payload:** `Invoice` (consumer view, status `void`) plus `cancellationInvoiceId` · **Permission:** subscribing needs `view-consumer-billing` > [!NOTE] > > Billing event. Requires a webhook endpoint subscribed by a consumer partner; payloads never include payment-provider messages. See [Billing webhooks](/consumer/billing#billing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # payment-failed Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/payment-failed openapi/consumer.v1.json webhook payment-failed A payment failed **Audience:** consumer · **Trigger:** a debit or payment attempt fails · **Payload:** payment summary `{resource, id, invoiceId, …}` · **Permission:** subscribing needs `view-consumer-billing` > [!NOTE] > > Billing event. Requires a webhook endpoint subscribed by a consumer partner; payloads never include payment-provider messages. See [Billing webhooks](/consumer/billing#billing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # payment-method-deactivated Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/payment-method-deactivated openapi/consumer.v1.json webhook payment-method-deactivated A SEPA mandate was deactivated **Audience:** consumer · **Trigger:** the mandate is revoked or fails hard · **Payload:** mandate summary `{resource, id, status, …}` · **Permission:** subscribing needs `view-consumer-billing` > [!NOTE] > > Billing event. Requires a webhook endpoint subscribed by a consumer partner; payloads never include payment-provider messages. See [Billing webhooks](/consumer/billing#billing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # price-terms-acceptance-required Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/price-terms-acceptance-required openapi/consumer.v1.json webhook price-terms-acceptance-required Price acceptance is required **Audience:** consumer · **Trigger:** a price change started with sets you have not accepted · **Payload:** `{resource, id, startsAt, voucherSetIds}` · **Permission:** subscribing needs `view-consumer-pricing` > [!NOTE] > > Pricing event. Subscribing requires `view-consumer-pricing`. See [Pricing webhooks](/consumer/prices#pricing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # price-terms-accepted Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/price-terms-accepted openapi/consumer.v1.json webhook price-terms-accepted Price Terms were accepted **Audience:** consumer · **Trigger:** a user of your partner accepts Price Terms · **Payload:** `PriceAcceptance` (consumer view) · **Permission:** subscribing needs `view-consumer-pricing` > [!NOTE] > > Pricing event. Subscribing requires `view-consumer-pricing`. See [Pricing webhooks](/consumer/prices#pricing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # price-terms-change-scheduled Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/price-terms-change-scheduled openapi/consumer.v1.json webhook price-terms-change-scheduled A price change was scheduled **Audience:** consumer · **Trigger:** AllRide schedules a change of your Price Terms · **Payload:** `{resource, id, startsAt, voucherSets}` · **Permission:** subscribing needs `view-consumer-pricing` > [!NOTE] > > Pricing event. Subscribing requires `view-consumer-pricing`. See [Pricing webhooks](/consumer/prices#pricing-webhooks). Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # redemption-created Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/redemption-created openapi/consumer.v1.json webhook redemption-created A redemption was made **Audience:** consumer · **Trigger:** A voucher is redeemed (any channel except import) · **Payload:** `Redemption` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # redemption-entity-created Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/redemption-entity-created openapi/consumer.v1.json webhook redemption-entity-created A redemption entity was created **Audience:** consumer · **Trigger:** An entity is created in the dashboard or by the email OTP gate · **Payload:** `RedemptionEntity` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # redemption-link-exhausted Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/redemption-link-exhausted openapi/consumer.v1.json webhook redemption-link-exhausted A redemption link reached its cap **Audience:** consumer · **Trigger:** A link reaches `maxRedemptions` or `maxTotalRedemptions` · **Payload:** `RedemptionLink` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # redemption-link-expired Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/redemption-link-expired openapi/consumer.v1.json webhook redemption-link-expired A redemption link expired **Audience:** consumer · **Trigger:** A link passes `expiresAt` · **Payload:** `RedemptionLink` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # redemption-voided Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/redemption-voided openapi/consumer.v1.json webhook redemption-voided A redemption was voided **Audience:** consumer · **Trigger:** A redemption is voided · **Payload:** `Redemption` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`. # webhook-endpoint-test Source: https://allride-voucher-consumer.mintlify.app/webhooks/events/webhook-endpoint-test openapi/consumer.v1.json webhook webhook-endpoint-test Test event sent from the dashboard or the API **Audience:** consumer · **Trigger:** you send a test event · **Payload:** `{resource, id, message}` Verify the `x-signing-signature` header before you trust the body ([Verifying signatures](/webhooks/verify-signatures)). Deliveries are at least once and unordered; deduplicate on `event.id`.