Fideliya API reference
Read and update your Fideliya business from your own systems with an API key from the API & Webhooks page (Enterprise). Every write needs an Idempotency-Key header; a message to customers is previewed, then confirmed; a till can take a payment from a gift card. The machine-readable spec is at /api/v1/openapi.json.
Authentication
Send your key on every request. Requests are made from your server: there are no CORS headers, so a key never belongs in a browser.
curl https://www.fideliyapass.com/api/v1/me \ -H "Authorization: Bearer $FIDELIYA_KEY"
Versioning
v1 changes are additive only: new endpoints, new optional parameters, new response fields and new enum values. Clients must ignore fields they do not know. A breaking change ships as /api/v2, and v1 then stays up for at least 12 months.
Errors and limits
Every error is application/problem+json with a stable code (see Errors). Every response carries Request-Id, and RateLimit / RateLimit-Policy say what remains of your key’s window.
A worked example: a till records a visit
The customer pays, the till sends the card it scanned, and the visit counts exactly as one at the counter: the pass in their wallet updates, and a reward is earned at the goal. The whole answer:
curl -X POST https://www.fideliyapass.com/api/v1/scans \
-H "Authorization: Bearer $FIDELIYA_KEY" \
-H "Idempotency-Key: 3f6c2a9e-8d1b-4c7a-9e0f-5b2d4a6c8e1f" \
-H "Content-Type: application/json" \
-d '{"card_id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b", "kind": "visit"}'{
"object": "scan_result",
"kind": "visit",
"card_id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"customer_id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"visits_added": 1,
"points_added": 0,
"points_deducted": 0,
"reward_earned": false,
"card": {
"id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"wallet": "apple",
"status": "active",
"visits": 8,
"points": 0,
"rewards_available": 0,
"rewards_redeemed": 1,
"last_visit_at": "2026-10-14T13:42:05.000Z",
"test": false,
"created_at": "2026-09-20T09:15:00.000Z"
}
}A worked example: a till takes a payment from a gift card
The customer pays with their gift card: the till sends the amount in minor units of the card’s currency ("amount_minor": 1250 with "currency": "EUR" is twelve euros fifty), with your ticket number if you want it kept. It is taken exactly as the scanner takes it at the counter: the card in their wallet updates, they get their receipt, and gift_card.spent fires. Retrying with the same Idempotency-Key never takes it twice, and two payments at once never take more than the card holds. The key needs the gift_cards:spend scope (“Take payments from gift cards” on the key form, or Full access): gift_cards:write only issues cards. The answer:
curl -X POST https://www.fideliyapass.com/api/v1/gift_cards/5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c/payments \
-H "Authorization: Bearer $FIDELIYA_KEY" \
-H "Idempotency-Key: 7c1e4b2a-9f3d-4e6a-8b5c-2d0f1a3e5c7b" \
-H "Content-Type: application/json" \
-d '{"amount_minor": 1250, "currency": "EUR", "reference": "T-1042"}'{
"object": "gift_card_payment",
"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"gift_card_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"amount": "12.50",
"amount_minor": 1250,
"currency": "EUR",
"balance_after": "20.00",
"reference": "T-1042",
"location_id": null,
"test": false,
"gift_card": {
"object": "gift_card",
"id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"status": "active",
"template_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"pass_id": null,
"currency": "EUR",
"initial_amount": "50.00",
"balance": "20.00",
"recipient": {
"name": "Léa Dubois",
"email": "lea@example.com"
},
"customer_id": null,
"customer_deleted": false,
"test": false,
"created_at": "2026-09-28T14:00:00.000Z",
"claimed_at": "2026-09-28T15:12:00.000Z",
"depleted_at": null
}
}More than the card holds is refused, and nothing is taken. A card not claimed yet, cancelled or used up is refused with its own code, and a currency that is not the card’s with currency_mismatch. There is no undo through the API: the five-second Undo belongs to the scanner that took the payment.
{
"type": "https://www.fideliyapass.com/developers/errors#insufficient_balance",
"title": "Not enough balance",
"status": 409,
"detail": "The card holds 20.00 EUR. Nothing was taken.",
"code": "insufficient_balance",
"param": "amount_minor",
"request_id": "req_4f1c2e8a9b3d4c6e8f0a1b2c3d4e5f6a",
"balance": {
"amount": "20.00",
"amount_minor": 2000,
"currency": "EUR"
}
}The key making the request
GET /api/v1/me
Returns the key's business, mode, scopes, the plan of the account that owns it, and its rate limits. Use it to check a key works.
Scope: read
Example response
{
"business": {
"id": "3f2c8d1e-5b6a-4c7d-9e8f-0a1b2c3d4e5f",
"name": "Café Lumière"
},
"key": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"prefix": "fdl_live_Ab3dEf"
},
"mode": "live",
"object": "key",
"plan": "pro",
"plan_name": "pro",
"rate_limits": {
"requests_per_business": 120,
"requests_per_key": 60,
"window_seconds": 60,
"writes_per_key": 15
},
"scopes": [
"read"
]
}Response schema
{
"$ref": "#/components/schemas/Me"
}Errors
List passes
GET /api/v1/passes
The business's loyalty cards, newest first, active and archived.
Scope: read
Example response
{
"data": [
{
"colors": {
"accent": "#34c759",
"background": "#1c1c1e",
"foreground": "#ffffff",
"label": "#c9a96e"
},
"cover_image_url": null,
"created_at": "2026-09-14T10:02:11.000Z",
"goal": 10,
"id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"locale": "en",
"logo_url": "https://www.fideliyapass.com/logos/example.png",
"loyalty_type": "visits",
"name": "Café Lumière coffee card",
"object": "pass",
"reward_title": "A free coffee",
"status": "active",
"tier_rewards": [],
"updated_at": "2026-10-01T08:30:00.000Z",
"wallets": {
"apple": true,
"google": true
}
}
],
"has_more": false,
"next_cursor": null,
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/PassList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Retrieve a pass
GET /api/v1/passes/{id}
One loyalty card of the business. An id from another business answers 404, as an unknown one does.
Scope: read
Example response
{
"colors": {
"accent": "#34c759",
"background": "#1c1c1e",
"foreground": "#ffffff",
"label": "#c9a96e"
},
"cover_image_url": null,
"created_at": "2026-09-14T10:02:11.000Z",
"goal": 10,
"id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"locale": "en",
"logo_url": "https://www.fideliyapass.com/logos/example.png",
"loyalty_type": "visits",
"name": "Café Lumière coffee card",
"object": "pass",
"reward_title": "A free coffee",
"status": "active",
"tier_rewards": [],
"updated_at": "2026-10-01T08:30:00.000Z",
"wallets": {
"apple": true,
"google": true
}
}Response schema
{
"$ref": "#/components/schemas/Pass"
}Errors
List customers
GET /api/v1/customers
The business's customers, newest first, each with their cards and balances. A customer is one person on one pass. Deleted customers are never listed.
Scope: read
Example response
{
"data": [
{
"cards": [
{
"created_at": "2026-09-20T09:15:00.000Z",
"id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"last_visit_at": "2026-10-07T17:42:05.000Z",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"points": 0,
"rewards_available": 0,
"rewards_redeemed": 1,
"status": "active",
"test": false,
"visits": 7,
"wallet": "apple"
}
],
"created_at": "2026-09-20T09:15:00.000Z",
"email": "ines@example.com",
"id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"name": "Inès Martin",
"object": "customer",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"phone": "+33 6 12 34 56 78",
"updated_at": null
}
],
"has_more": true,
"next_cursor": "WyIyMDI2LTA5LTIwVDA5OjE1OjAwLjAwMDAwMFoiLCI0YzFkOWUyYSJd.c2lnbmF0dXJlLWV4YW1wbGUtb25seS1ub3QtYS1yZWFsLW9uZQ",
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/CustomerList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Retrieve a customer
GET /api/v1/customers/{id}
One customer of the business, with their cards and balances. A deleted customer, or an id from another business, answers 404.
Scope: read
Example response
{
"cards": [
{
"created_at": "2026-09-20T09:15:00.000Z",
"id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"last_visit_at": "2026-10-07T17:42:05.000Z",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"points": 0,
"rewards_available": 0,
"rewards_redeemed": 1,
"status": "active",
"test": false,
"visits": 7,
"wallet": "apple"
}
],
"created_at": "2026-09-20T09:15:00.000Z",
"email": "ines@example.com",
"id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"name": "Inès Martin",
"object": "customer",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"phone": "+33 6 12 34 56 78",
"updated_at": null
}Response schema
{
"$ref": "#/components/schemas/Customer"
}Errors
List scans
GET /api/v1/scans
Counter events on the business's cards, newest first: visits, and points added or deducted. The loyalty events the dashboard counts as Scans: a test card's are left out; a deleted customer's stay, by id only. Reward redemptions are not scans. Gift-card spending, which the dashboard's Scans figure also counts, is on each card's ledger at /gift_cards/{id}.
Scope: read
Example response
{
"data": [
{
"amount": "4.50",
"created_at": "2026-10-07T17:42:05.000Z",
"customer_deleted": false,
"customer_id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"id": "2f8e4d6c-1b3a-4c5d-9e7f-8a9b0c1d2e3f",
"kind": "visit",
"location_id": null,
"object": "scan",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"points": 0
}
],
"has_more": false,
"next_cursor": null,
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/ScanList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
List locations
GET /api/v1/locations
The business's counters (branches), newest first, switched-off ones included. No team-member links, no join codes, no review links.
Scope: read
Example response
{
"data": [
{
"active": true,
"address": "12 Rue Oberkampf",
"city": "Paris",
"country": "FR",
"created_at": "2026-09-14T10:05:00.000Z",
"id": "8d3c2b1a-0f9e-4d8c-b7a6-5e4d3c2b1a0f",
"latitude": 48.8645,
"longitude": 2.3727,
"name": "Café Lumière, Rue Oberkampf",
"object": "location",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d"
}
],
"has_more": false,
"next_cursor": null,
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/LocationList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
List referrals
GET /api/v1/referrals
Referrals on the business's passes, newest first: the ones the dashboard's referral page counts (a test card at either end is left out). A deleted customer appears by id only.
Scope: read
Example response
{
"data": [
{
"completed_at": "2026-10-03T12:20:00.000Z",
"created_at": "2026-10-02T09:00:00.000Z",
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "referral",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"referred_bonus": 1,
"referred_customer_id": "7e6d5c4b-3a2f-4e1d-9c8b-7a6f5e4d3c2b",
"referred_deleted": false,
"referrer_bonus": 1,
"referrer_customer_id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"referrer_deleted": false,
"status": "completed"
}
],
"has_more": false,
"next_cursor": null,
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/ReferralList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
List gift cards
GET /api/v1/gift_cards
The business's gift cards, newest first, test cards included (flagged). Never a code, token or serial that can spend or claim a card. A deleted customer's card answers its recipient as null.
Scope: read
Example response
{
"data": [
{
"balance": "32.50",
"claimed_at": "2026-09-28T15:12:00.000Z",
"created_at": "2026-09-28T14:00:00.000Z",
"currency": "EUR",
"customer_deleted": false,
"customer_id": null,
"depleted_at": null,
"id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"initial_amount": "50.00",
"object": "gift_card",
"pass_id": null,
"recipient": {
"email": "lea@example.com",
"name": "Léa Dubois"
},
"status": "active",
"template_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"test": false
}
],
"has_more": false,
"next_cursor": null,
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/GiftCardList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Retrieve a gift card
GET /api/v1/gift_cards/{id}
One gift card of the business, with every payment taken with it (newest first). This is where gift-card spending is listed; /scans lists loyalty visits and points. An id from another business answers 404.
Scope: read
Example response
{
"balance": "32.50",
"claimed_at": "2026-09-28T15:12:00.000Z",
"created_at": "2026-09-28T14:00:00.000Z",
"currency": "EUR",
"customer_deleted": false,
"customer_id": null,
"depleted_at": null,
"id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"initial_amount": "50.00",
"object": "gift_card",
"pass_id": null,
"recipient": {
"email": "lea@example.com",
"name": "Léa Dubois"
},
"status": "active",
"template_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"test": false,
"transactions": [
{
"amount": "17.50",
"balance_after": "32.50",
"created_at": "2026-10-02T11:40:00.000Z",
"id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"location_id": "8d3c2b1a-0f9e-4d8c-b7a6-5e4d3c2b1a0f",
"reference": "T-1042",
"type": "spend"
}
],
"transactions_has_more": false
}Response schema
{
"$ref": "#/components/schemas/GiftCardWithLedger"
}Errors
List webhook endpoints
GET /api/v1/webhook_endpoints
The business's webhook endpoints, newest first, with their status and last delivery. Read only: endpoints are managed in the dashboard. Never a signing secret; URLs without their query string.
Scope: read
Example response
{
"data": [
{
"consecutive_failures": 0,
"created_at": "2026-09-15T08:00:00.000Z",
"events": [
"customer.created",
"scan.recorded"
],
"failing_since": null,
"format": "v2",
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"last_delivery": {
"at": "2026-10-08T16:02:11.000Z",
"http_status": 200,
"status": "succeeded"
},
"last_success_at": "2026-10-08T16:02:11.000Z",
"object": "webhook_endpoint",
"paused_at": null,
"paused_reason": null,
"status": "active",
"url": "https://example.com/fideliya/events"
}
],
"has_more": false,
"next_cursor": null,
"object": "list"
}Response schema
{
"$ref": "#/components/schemas/WebhookEndpointList"
}Errors
- 400 invalid_cursor, invalid_parameter
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Figures for a period
GET /api/v1/stats
The figures the dashboard's Analytics page shows for the same days, computed by the same code: scans, visits, active, returning and new customers, rewards and gift cards. Whole days in the business's time zone; the last 30 by default, at most 366. `from` after `to` is 400 invalid_parameter.
Scope: read
Example response
{
"active_customers": 412,
"from": "2026-09-01",
"gift_cards_issued": 6,
"new_customers": 97,
"object": "stats",
"returning_customers": 236,
"rewards_earned": 58,
"rewards_redeemed": 41,
"scans": 1284,
"time_zone": "Europe/Paris",
"to": "2026-09-30",
"visits": 1102
}Response schema
{
"$ref": "#/components/schemas/Stats"
}Errors
Enrol a customer
POST /api/v1/customers
Enrols a person on one of the business's passes, exactly as the pass's join form does: the plan's customer limit applies, the owner's team is told as for any new customer, and webhooks fire. Nothing is sent to the customer unless `send_welcome` is true. An email already on the pass answers 409 customer_exists and changes nothing.
Scope: customers:write
Header: Idempotency-Key: <a UUID per write>
Example request
{
"email": "ines@example.com",
"name": "Inès Martin",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"send_welcome": true,
"wallet": "apple"
}Request schema
{
"$ref": "#/components/schemas/CustomerCreate"
}Example response
{
"cards": [
{
"created_at": "2026-09-20T09:15:00.000Z",
"id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"last_visit_at": null,
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"points": 0,
"rewards_available": 0,
"rewards_redeemed": 0,
"status": "active",
"test": false,
"visits": 0,
"wallet": "apple"
}
],
"created_at": "2026-09-20T09:15:00.000Z",
"email": "ines@example.com",
"id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"name": "Inès Martin",
"object": "customer",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"phone": "+33 6 12 34 56 78",
"updated_at": null
}Response schema
{
"$ref": "#/components/schemas/Customer"
}Errors
- 400 idempotency_key_required
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden, customer_limit_reached
- 404 not_found
- 409 idempotency_in_progress, customer_exists
- 422 idempotency_key_reused, validation_failed
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Record a scan
POST /api/v1/scans
The counter action, by the scanner's own rules: a visit, points added (up to the goal) or deducted (up to the balance), or a reward redeemed. The customer's wallet card updates as after a scan at the counter, and webhooks fire. A refused scan records nothing. A `visit` less than 15 minutes after the card's last one answers 409 `recent_visit` (with `last_visit_at`) unless the request carries `"confirm_repeat": true`, as the scanner asks "same visit or new visit?". The Idempotency-Key replay is unchanged: the same key returns the first answer.
Scope: scans:write
Header: Idempotency-Key: <a UUID per write>
Example request
{
"amount": 4.5,
"card_id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"kind": "visit"
}Request schema
{
"$ref": "#/components/schemas/ScanCreate"
}Example response
{
"card": {
"created_at": "2026-09-20T09:15:00.000Z",
"id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"last_visit_at": "2026-10-10T18:02:00.000Z",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"points": 0,
"rewards_available": 0,
"rewards_redeemed": 1,
"status": "active",
"test": false,
"visits": 8,
"wallet": "apple"
},
"card_id": "6e2a1b3c-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"customer_id": "4c1d9e2a-7b3f-4a6e-8d5c-1f2e3a4b5c6d",
"kind": "visit",
"object": "scan_result",
"points_added": 0,
"points_deducted": 0,
"reward_earned": false,
"visits_added": 1
}Response schema
{
"$ref": "#/components/schemas/ScanResult"
}Errors
- 400 idempotency_key_required
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 404 not_found
- 409 idempotency_in_progress, card_not_active, insufficient_balance, no_reward_available, scan_refused, recent_visit
- 422 idempotency_key_reused, validation_failed
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Issue a gift card
POST /api/v1/gift_cards
Issues a gift card, exactly as the dashboard's gift-card page does. ⚠️ It emails the recipient their card, in `locale`, and the answer says whether that email went: `email.sent`, and when it did not, `email.reason` (`not_configured`, `sandbox`, `blocked_environment`, `invalid_address`, `rate_limited`, `provider_refused` or `failed`). The card is issued either way; tell the owner when `email.sent` is false: they can send it again from the dashboard's gift cards. The card takes its look from the business's pass (409 pass_required without one).
Scope: gift_cards:write
Header: Idempotency-Key: <a UUID per write>
Example request
{
"amount": 50,
"currency": "EUR",
"locale": "fr",
"recipient": {
"email": "lea@example.com",
"name": "Léa Dubois"
}
}Request schema
{
"$ref": "#/components/schemas/GiftCardCreate"
}Example response
{
"balance": "50.00",
"claimed_at": null,
"created_at": "2026-09-28T14:00:00.000Z",
"currency": "EUR",
"customer_deleted": false,
"customer_id": null,
"depleted_at": null,
"email": {
"reason": null,
"sent": true
},
"id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"initial_amount": "50.00",
"object": "gift_card",
"pass_id": null,
"recipient": {
"email": "lea@example.com",
"name": "Léa Dubois"
},
"status": "issued",
"template_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"test": false
}Response schema
{
"$ref": "#/components/schemas/GiftCardCreated"
}Errors
- 400 idempotency_key_required
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 409 idempotency_in_progress, pass_required
- 422 idempotency_key_reused, validation_failed
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Take a payment from a gift card
POST /api/v1/gift_cards/{id}/payments
Takes an amount off the card exactly as the scanner does at the counter: the payment goes in the card's ledger, the card in the customer's wallet updates, the recipient gets their receipt, and the gift_card.spent webhook fires. The amount is in minor units of the card's own currency, and never more than its balance: taking exactly the balance uses the card up (`depleted`). A card not claimed yet, cancelled or used up cannot pay. Two payments at once on one card never take more than it holds. There is no undo through the API: the five-second Undo is the scanner's, on the device that took the payment. Needs the `gift_cards:spend` scope (on the key form, "Take payments from gift cards", or Full access): `gift_cards:write` only issues cards.
Scope: gift_cards:spend
Header: Idempotency-Key: <a UUID per write>
Example request
{
"amount_minor": 1250,
"currency": "EUR",
"reference": "T-1042"
}Request schema
{
"$ref": "#/components/schemas/GiftCardPaymentCreate"
}Example response
{
"amount": "12.50",
"amount_minor": 1250,
"balance_after": "20.00",
"currency": "EUR",
"gift_card": {
"balance": "20.00",
"claimed_at": "2026-09-28T15:12:00.000Z",
"created_at": "2026-09-28T14:00:00.000Z",
"currency": "EUR",
"customer_deleted": false,
"customer_id": null,
"depleted_at": null,
"id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"initial_amount": "50.00",
"object": "gift_card",
"pass_id": null,
"recipient": {
"email": "lea@example.com",
"name": "Léa Dubois"
},
"status": "active",
"template_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"test": false
},
"gift_card_id": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"location_id": null,
"object": "gift_card_payment",
"reference": "T-1042",
"test": false
}Response schema
{
"$ref": "#/components/schemas/GiftCardPayment"
}Errors
- 400 idempotency_key_required
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 404 not_found
- 409 idempotency_in_progress, insufficient_balance, gift_card_not_claimed, gift_card_cancelled, gift_card_depleted
- 422 idempotency_key_reused, validation_failed, currency_mismatch
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Preview a message to customers
POST /api/v1/broadcasts
Checks a message to the card holders of a pass and answers what would be sent, to how many, and the quota left, with a `confirmation_token`. ⛔ It sends nothing. To send, call POST /broadcasts/confirm with the token and the same message within 10 minutes.
Scope: broadcasts:send
Header: Idempotency-Key: <a UUID per write>
Example request
{
"audience": "active",
"body": "Our autumn menu is here: come and taste it this week.",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"title": "Autumn menu"
}Request schema
{
"$ref": "#/components/schemas/BroadcastCreate"
}Example response
{
"confirmation_token": "fdc_EXAMPLEtokenEXAMPLEtokenEXAMPLEtokenEXAMPL",
"expires_at": "2026-10-10T18:12:00.000Z",
"message": {
"body": "Our autumn menu is here: come and taste it this week.",
"title": "Autumn menu",
"translated": false
},
"object": "broadcast_preview",
"quota": {
"remaining": 3,
"total": 4
},
"recipients": {
"audience": "active",
"cards": 214,
"people": 198
},
"scheduled_at": null
}Response schema
{
"$ref": "#/components/schemas/BroadcastPreview"
}Errors
- 400 idempotency_key_required
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden, broadcast_not_in_plan, broadcast_quota_reached
- 404 not_found
- 409 idempotency_in_progress
- 422 idempotency_key_reused, validation_failed
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Send a previewed message
POST /api/v1/broadcasts/confirm
Sends the message previewed with POST /broadcasts: the same message, with its `confirmation_token` (single use, 10 minutes, only for the key that asked). It goes out exactly as from the dashboard (quota, schedule, wallet and email fallback), and the owner is told by a notice and an email naming the key. A changed message, a used or expired token, or another key's token is refused, each with its own code, and nothing is sent.
Scope: broadcasts:send
Header: Idempotency-Key: <a UUID per write>
Example request
{
"audience": "active",
"body": "Our autumn menu is here: come and taste it this week.",
"confirmation_token": "fdc_EXAMPLEtokenEXAMPLEtokenEXAMPLEtokenEXAMPL",
"pass_id": "9b2f7c3e-1a4d-4e8b-9c6f-2d7e8a1b3c5d",
"title": "Autumn menu"
}Request schema
{
"$ref": "#/components/schemas/BroadcastConfirm"
}Example response
{
"id": "7d6c5b4a-3f2e-4d1c-8b0a-9f8e7d6c5b4a",
"object": "broadcast",
"recipient_count": 214,
"scheduled_at": null,
"sent_count": 0,
"status": "sending"
}Response schema
{
"$ref": "#/components/schemas/Broadcast"
}Errors
- 400 idempotency_key_required, confirmation_token_invalid
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden, broadcast_not_in_plan, broadcast_quota_reached, confirmation_principal_mismatch, confirmation_business_mismatch
- 404 not_found
- 409 idempotency_in_progress, confirmation_token_used, confirmation_audience_changed
- 410 confirmation_token_expired
- 422 idempotency_key_reused, validation_failed, confirmation_payload_changed
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable
Cancel a scheduled message
POST /api/v1/broadcasts/{id}/cancel
Cancels a message scheduled for later (POST /broadcasts/confirm with `scheduled_at` answers its `id`), whoever scheduled it: the dashboard, a key or an assistant. Nobody receives it, and it stops counting against the month's messages. Cancelling again answers the same. A message already sent, being sent right now, or sent at once is refused (409 broadcast_already_sent). Revoking the key that scheduled a message, or disconnecting the assistant that did, cancels it too.
Scope: broadcasts:send
Header: Idempotency-Key: <a UUID per write>
Example request
{}Request schema
{
"$ref": "#/components/schemas/BroadcastCancel"
}Example response
{
"cancelled_at": "2026-10-20T16:41:00.000Z",
"id": "7d6c5b4a-3f2e-4d1c-8b0a-9f8e7d6c5b4a",
"object": "broadcast",
"scheduled_at": "2026-10-24T08:00:00.000Z",
"status": "cancelled"
}Response schema
{
"$ref": "#/components/schemas/BroadcastCancelled"
}Errors
- 400 idempotency_key_required
- 401 authentication_required, invalid_api_key, api_key_revoked, api_key_expired, test_key_not_supported, not_supported_yet
- 403 plan_required, key_mode_mismatch, insufficient_scope, forbidden
- 404 not_found
- 409 idempotency_in_progress, broadcast_already_sent
- 422 idempotency_key_reused, validation_failed
- 429 rate_limited
- 500 internal_error
- 503 rate_limiter_unavailable, service_unavailable