{
  "openapi": "3.1.0",
  "info": {
    "title": "Fidelia POS API",
    "version": "1.0.0",
    "summary": "Read and update Fidelia loyalty cards from a cash register.",
    "description": "Cash registers scan the customer's Fidelia card (QR or barcode) and add stamps, points, cashback, membership check-ins, prepaid payments and pack items. All amounts are integers in minor units (cents / öre) of the shop's currency. Guides: https://fidelia.se/developers/",
    "x-description-fr": "Les caisses scannent la carte Fidelia du client (QR ou code-barres) et ajoutent tampons, points, cashback, passages d'abonnement, paiements prépayés et articles de carnet. Tous les montants sont des entiers en centimes (unités mineures) de la devise du commerce. Guides : https://fidelia.se/developers/fr/",
    "contact": {
      "name": "Fidelia",
      "email": "support@fidelia.se",
      "url": "https://fidelia.se/developers/"
    }
  },
  "servers": [
    {
      "url": "https://api.fidelia.se/pos/v1",
      "description": "Live and sandbox (the key decides which shop)"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Shop",
      "description": "The shop behind the key.",
      "x-description-fr": "Le commerce lié à la clé."
    },
    {
      "name": "Cards",
      "description": "Look up a card and act on it.",
      "x-description-fr": "Lire une carte et agir dessus."
    },
    {
      "name": "Receipts",
      "description": "What a receipt did; refunds.",
      "x-description-fr": "Ce qu'un ticket a fait ; remboursements."
    },
    {
      "name": "Pairing",
      "description": "Connect a till without typing a key.",
      "x-description-fr": "Connecter une caisse sans taper de clé."
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Shop"
        ],
        "summary": "The shop, its locations and cards",
        "x-summary-fr": "Le commerce, ses établissements et ses cartes",
        "description": "Call it when the till starts: the shop's currency and languages, its locations, and every active card program with its till rules (code format, rewards and their POS products).",
        "x-description-fr": "À appeler au démarrage de la caisse : devise et langues du commerce, établissements, et chaque programme de carte actif avec ses règles de caisse (format du code, récompenses et articles de caisse).",
        "parameters": [
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}": {
      "get": {
        "operationId": "getCard",
        "tags": [
          "Cards"
        ],
        "summary": "Look up a card",
        "x-summary-fr": "Lire une carte",
        "description": "`code` is what the scanner read: the 32-character serial (QR) or the 13-digit card number (barcode). Returns the customer's first name, balances, rewards ready to use and, for memberships, the discount to apply to the basket.",
        "x-description-fr": "`code` est ce que le scanner a lu : le numéro de série de 32 caractères (QR) ou le numéro de carte à 13 chiffres (code-barres). Renvoie le prénom du client, les soldes, les récompenses disponibles et, pour les abonnements, la remise à appliquer au panier.",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "card"
                  ],
                  "properties": {
                    "card": {
                      "$ref": "#/components/schemas/Card"
                    }
                  }
                },
                "example": {
                  "card": {
                    "serial": "5ed54e2341c470b1cbcc5c6b4db9962b",
                    "card_number": "6309933405549",
                    "type": "stamps",
                    "program": {
                      "id": "a7878d4a-06a1-48df-b3a2-1587479172fd",
                      "name": "Loyalty card"
                    },
                    "customer": {
                      "first_name": "Sarah",
                      "locale": "fr"
                    },
                    "currency": "EUR",
                    "tier": null,
                    "cooldown_remaining_seconds": 0,
                    "stamps": {
                      "count": 9,
                      "required": 10,
                      "cycle": 2,
                      "reward_ready": false,
                      "reward": "Free coffee"
                    },
                    "rewards_available": []
                  }
                }
              }
            },
            "x-description-fr": "OK"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/sale": {
      "post": {
        "operationId": "sale",
        "tags": [
          "Cards"
        ],
        "summary": "Record a sale",
        "x-summary-fr": "Enregistrer une vente",
        "description": "Send it once the receipt is closed. Fidelia applies the card's till rules: a stamp (stamp cards, from the minimum amount), points (points cards), cashback credit on the part not paid with the card balance, a check-in (memberships). Prepaid and pack cards earn nothing on a sale. A sale never fails for a business reason: `applied` is false with a `reason` instead (e.g. `cooldown`, `reward_ready`, `below_min_amount`).",
        "x-description-fr": "À envoyer à la clôture du ticket. Fidelia applique les règles de caisse de la carte : un tampon (cartes à tampons, à partir du montant minimum), des points (cartes à points), du cashback sur la partie non payée avec le solde de la carte, un passage (abonnements). Les cartes prépayées et carnets ne gagnent rien sur une vente. Une vente n'échoue jamais pour une raison métier : `applied` vaut false avec une `reason` (ex. `cooldown`, `reward_ready`, `below_min_amount`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaleRequest"
              },
              "example": {
                "amount": 1250,
                "receipt_ref": "T1-000042"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/rewards/{reward_id}/redeem": {
      "post": {
        "operationId": "redeemReward",
        "tags": [
          "Cards"
        ],
        "summary": "Use a reward",
        "x-summary-fr": "Utiliser une récompense",
        "description": "Stamp cards: `reward_id` is `ready` (the card is full) or `milestone:<id>` (a smaller reward on the way, listed in `stamps.milestones`; the card keeps collecting). Points cards: one of the program's reward ids (see GET /me). The answer carries the POS product the owner linked to the reward (`product.ref`, `product.value`) so the till can add the free item.",
        "x-description-fr": "Cartes à tampons : `reward_id` vaut `ready` (carte pleine) ou `milestone:<id>` (une récompense intermédiaire, listée dans `stamps.milestones` ; la carte continue de se remplir). Cartes à points : l'id d'une récompense du programme (voir GET /me). La réponse contient l'article de caisse lié par le commerçant (`product.ref`, `product.value`) pour que la caisse ajoute l'article offert.",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "name": "reward_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "example": "ready",
            "description": "`ready`, `milestone:<id>` or a points reward id",
            "x-description-fr": "`ready`, `milestone:<id>` ou l'id d'une récompense à points"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RedeemRequest"
              },
              "example": {
                "receipt_ref": "T1-000042"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/balance/spend": {
      "post": {
        "operationId": "spendBalance",
        "tags": [
          "Cards"
        ],
        "summary": "Pay with the card balance",
        "x-summary-fr": "Payer avec le solde de la carte",
        "description": "Cashback and prepaid cards. `amount` is a number of minor units or `\"all\"`; never more than `receipt_total` when given. Treat the result's `spent` as a payment line (split tender: the customer pays the rest another way). Send it before the sale on the same `receipt_ref`, so cashback isn't earned on the part paid with the balance.",
        "x-description-fr": "Cartes cashback et prépayées. `amount` est un nombre d'unités mineures ou `\"all\"` ; jamais plus que `receipt_total` s'il est donné. Traitez `spent` comme une ligne de paiement (paiement fractionné : le client paie le reste autrement). Envoyez-le avant la vente sur le même `receipt_ref`, pour que le cashback ne soit pas gagné sur la partie payée avec le solde.",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpendRequest"
              },
              "example": {
                "amount": "all",
                "receipt_total": 1250,
                "receipt_ref": "T1-000042"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/balance/topup": {
      "post": {
        "operationId": "topUp",
        "tags": [
          "Cards"
        ],
        "summary": "Top up a prepaid card",
        "x-summary-fr": "Recharger une carte prépayée",
        "description": "The customer paid `amount` at the till; the card is credited `amount` plus the program's bonus (`credited`).",
        "x-description-fr": "Le client a payé `amount` en caisse ; la carte est créditée de `amount` plus le bonus du programme (`credited`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AmountRequest"
              },
              "example": {
                "amount": 2000,
                "receipt_ref": "T1-000043"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/membership/renew": {
      "post": {
        "operationId": "renewMembership",
        "tags": [
          "Cards"
        ],
        "summary": "Renew or activate a membership",
        "x-summary-fr": "Renouveler ou activer un abonnement",
        "description": "Extends the membership by the program's validity, or activates a membership waiting for payment.",
        "x-description-fr": "Prolonge l'abonnement de la durée du programme, ou active un abonnement en attente de paiement.",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReceiptOnlyRequest"
              },
              "example": {
                "receipt_ref": "T1-000044"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/pack/sell": {
      "post": {
        "operationId": "sellPack",
        "tags": [
          "Cards"
        ],
        "summary": "Sell or activate a pack",
        "x-summary-fr": "Vendre ou activer un carnet",
        "description": "A pack waiting for payment is activated; an active one gets `count` more packs.",
        "x-description-fr": "Un carnet en attente de paiement est activé ; un carnet actif reçoit `count` carnets de plus.",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CountRequest"
              },
              "example": {
                "count": 1,
                "receipt_ref": "T1-000045"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/cards/{code}/pack/use": {
      "post": {
        "operationId": "usePack",
        "tags": [
          "Cards"
        ],
        "summary": "Use pack items",
        "x-summary-fr": "Utiliser des articles du carnet",
        "description": "Takes `count` items (1–10) from an active pack.",
        "x-description-fr": "Retire `count` articles (1–10) d'un carnet actif.",
        "parameters": [
          {
            "$ref": "#/components/parameters/code"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CountRequest"
              },
              "example": {
                "count": 1,
                "receipt_ref": "T1-000046"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/receipts/{receipt_ref}": {
      "get": {
        "operationId": "getReceipt",
        "tags": [
          "Receipts"
        ],
        "summary": "What a receipt did",
        "x-summary-fr": "Ce qu'un ticket a fait",
        "description": "Every ledger row of this shop with this receipt number, reversals included.",
        "x-description-fr": "Toutes les lignes du commerce avec ce numéro de ticket, annulations comprises.",
        "parameters": [
          {
            "$ref": "#/components/parameters/receiptRef"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Receipt"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/receipts/{receipt_ref}/reverse": {
      "post": {
        "operationId": "reverseReceipt",
        "tags": [
          "Receipts"
        ],
        "summary": "Refund / cancel a receipt",
        "x-summary-fr": "Rembourser / annuler un ticket",
        "description": "Undoes everything the receipt did on Fidelia cards (stamps, points, cashback, balance spent, rewards used, pack items) if it is less than 24 hours old. Refused with `reverse_conflict` when the card moved on since (the reward was claimed, the points were spent): correct it in the Fidelia dashboard.",
        "x-description-fr": "Annule tout ce que le ticket a fait sur les cartes Fidelia (tampons, points, cashback, solde utilisé, récompenses, articles de carnet) s'il a moins de 24 heures. Refusé avec `reverse_conflict` si la carte a évolué depuis (récompense utilisée, points dépensés) : corrigez-la dans le tableau de bord Fidelia.",
        "parameters": [
          {
            "$ref": "#/components/parameters/receiptRef"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/device"
          },
          {
            "$ref": "#/components/parameters/lang"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReverseResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/pairing": {
      "post": {
        "operationId": "startPairing",
        "tags": [
          "Pairing"
        ],
        "summary": "Start pairing",
        "x-summary-fr": "Démarrer le jumelage",
        "description": "No key needed. Show `code` on the till's screen and ask the shop owner to type it in Fidelia → Tills & POS. Keep `pairing_id` and `poll_secret` and poll GET /pairing/{pairing_id}. Codes last 10 minutes.",
        "x-description-fr": "Aucune clé nécessaire. Affichez `code` sur l'écran de la caisse et demandez au commerçant de le taper dans Fidelia → Caisses & POS. Gardez `pairing_id` et `poll_secret` et interrogez GET /pairing/{pairing_id}. Un code dure 10 minutes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PairingRequest"
              },
              "example": {
                "device_name": "Kassa 1",
                "partner_id": "pt_0123456789abcdef01234567"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pairing"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        },
        "security": []
      }
    },
    "/pairing/{pairing_id}": {
      "get": {
        "operationId": "pollPairing",
        "tags": [
          "Pairing"
        ],
        "summary": "Collect the key",
        "x-summary-fr": "Récupérer la clé",
        "description": "Send `Authorization: Pairing <poll_secret>` every 3 seconds. 202 while the owner hasn't entered the code; 200 once with the key (store it securely: it is never shown again); 410 when the code expired.",
        "x-description-fr": "Envoyez `Authorization: Pairing <poll_secret>` toutes les 3 secondes. 202 tant que le commerçant n'a pas tapé le code ; 200 une seule fois avec la clé (stockez-la de façon sûre : elle n'est plus jamais affichée) ; 410 si le code a expiré.",
        "parameters": [
          {
            "name": "pairing_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PairingResult"
                }
              }
            },
            "x-description-fr": "OK"
          },
          "202": {
            "description": "Waiting for the owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "pending"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            },
            "x-description-fr": "En attente du commerçant"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "410": {
            "$ref": "#/components/responses/E410"
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer fk_live_…` (or `fk_test_…` for the sandbox). One key per till.",
        "x-description-fr": "`Authorization: Bearer fk_live_…` (ou `fk_test_…` pour le bac à sable). Une clé par caisse."
      }
    },
    "parameters": {
      "code": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "Serial (QR, 32 hex) or card number (barcode, 13 digits).",
        "x-description-fr": "Numéro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).",
        "schema": {
          "type": "string"
        },
        "example": "6309933405549"
      },
      "receiptRef": {
        "name": "receipt_ref",
        "in": "path",
        "required": true,
        "description": "The till's receipt number (unique per shop).",
        "x-description-fr": "Le numéro de ticket de la caisse (unique par commerce).",
        "schema": {
          "type": "string",
          "maxLength": 64
        },
        "example": "T1-000042"
      },
      "idempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Unique per action (e.g. a UUID). A retry with the same key and body replays the first answer (header `Idempotent-Replayed: true`); kept 24 h.",
        "x-description-fr": "Unique par action (ex. un UUID). Une nouvelle tentative avec la même clé et le même corps renvoie la première réponse (en-tête `Idempotent-Replayed: true`) ; conservée 24 h.",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "device": {
        "name": "Fidelia-Device",
        "in": "header",
        "required": false,
        "description": "Till name shown in the shop's history (default: the key's name).",
        "x-description-fr": "Nom de la caisse affiché dans l'historique (par défaut : le nom de la clé).",
        "schema": {
          "type": "string",
          "maxLength": 200
        }
      },
      "lang": {
        "name": "lang",
        "in": "query",
        "required": false,
        "description": "Language of reward and card names (one of the shop's languages; also read from Accept-Language).",
        "x-description-fr": "Langue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).",
        "schema": {
          "type": "string",
          "enum": [
            "sv",
            "en",
            "nl",
            "fr"
          ]
        }
      }
    },
    "schemas": {
      "CardType": {
        "type": "string",
        "enum": [
          "stamps",
          "points",
          "cashback",
          "membership",
          "prepaid",
          "pack"
        ]
      },
      "Card": {
        "type": "object",
        "required": [
          "serial",
          "card_number",
          "type",
          "program",
          "customer",
          "currency",
          "rewards_available",
          "cooldown_remaining_seconds"
        ],
        "properties": {
          "serial": {
            "type": "string"
          },
          "card_number": {
            "type": "string",
            "pattern": "^[0-9]{13}$"
          },
          "type": {
            "$ref": "#/components/schemas/CardType"
          },
          "program": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "customer": {
            "type": "object",
            "properties": {
              "first_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "locale": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR",
              "SEK"
            ]
          },
          "tier": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "perk": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "cooldown_remaining_seconds": {
            "type": "integer",
            "description": "Seconds before the next stamp / check-in counts."
          },
          "rewards_available": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "points": {
                  "type": "integer"
                }
              }
            },
            "description": "Ask the customer \"Use reward now?\" when not empty."
          },
          "stamps": {
            "type": "object",
            "description": "Stamp cards.",
            "properties": {
              "count": {
                "type": "integer"
              },
              "required": {
                "type": "integer"
              },
              "cycle": {
                "type": "integer"
              },
              "reward_ready": {
                "type": "boolean"
              },
              "reward": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "milestones": {
                "type": "array",
                "description": "Smaller rewards on the way (this round of stamps).",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "example": "milestone:cookie"
                    },
                    "stamps": {
                      "type": "integer"
                    },
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "claimed": {
                      "type": "boolean"
                    },
                    "available": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "points": {
            "type": "object",
            "description": "Points cards.",
            "properties": {
              "balance": {
                "type": "integer"
              },
              "spend_per_point": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "rewards": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "points": {
                      "type": "integer"
                    },
                    "available": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "balance": {
            "type": "object",
            "description": "Cashback and prepaid cards (minor units).",
            "properties": {
              "amount": {
                "type": "integer"
              },
              "percent": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "bonus_percent": {
                "type": "integer"
              }
            }
          },
          "membership": {
            "type": "object",
            "description": "Membership cards: apply discount_percent to the basket while active.",
            "properties": {
              "active": {
                "type": "boolean"
              },
              "waiting_activation": {
                "type": "boolean"
              },
              "valid_until": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date"
              },
              "discount_percent": {
                "type": "integer"
              },
              "perk": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "pack": {
            "type": "object",
            "description": "Pack cards.",
            "properties": {
              "items_left": {
                "type": "integer"
              },
              "items_per_pack": {
                "type": "integer"
              },
              "item": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "active": {
                "type": "boolean"
              },
              "price": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "shop": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "enum": [
                  "BE",
                  "SE"
                ]
              },
              "currency": {
                "type": "string"
              },
              "timezone": {
                "type": "string"
              },
              "default_locale": {
                "type": "string"
              },
              "locales": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "locations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "address": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "programs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "type": {
                  "$ref": "#/components/schemas/CardType"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "code_format": {
                  "type": "string",
                  "enum": [
                    "qr",
                    "barcode"
                  ]
                },
                "tiers": {
                  "type": "array"
                },
                "rewards": {
                  "type": "array"
                },
                "reward": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "stamps_required": {
                  "type": "integer"
                },
                "percent": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "discount_percent": {
                  "type": "integer"
                },
                "bonus_percent": {
                  "type": "integer"
                },
                "price": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "milestones": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "stamps": {
                        "type": "integer"
                      },
                      "label": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "client": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ]
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "location_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "partner": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "ReceiptRef": {
        "type": "string",
        "pattern": "^[!-~]{1,64}$",
        "description": "Unique per shop (prefix the till if your numbering is per till, e.g. T1-000042)."
      },
      "SaleRequest": {
        "type": "object",
        "required": [
          "amount",
          "receipt_ref"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000,
            "description": "Receipt total, minor units."
          },
          "receipt_ref": {
            "$ref": "#/components/schemas/ReceiptRef"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "RedeemRequest": {
        "type": "object",
        "properties": {
          "also_stamp": {
            "type": "boolean",
            "description": "Stamp cards: also stamp today's visit."
          },
          "receipt_ref": {
            "$ref": "#/components/schemas/ReceiptRef"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "SpendRequest": {
        "type": "object",
        "required": [
          "amount"
        ],
        "properties": {
          "amount": {
            "oneOf": [
              {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000000
              },
              {
                "const": "all"
              }
            ]
          },
          "receipt_total": {
            "type": "integer",
            "minimum": 0
          },
          "receipt_ref": {
            "$ref": "#/components/schemas/ReceiptRef"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "AmountRequest": {
        "type": "object",
        "required": [
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000
          },
          "receipt_ref": {
            "$ref": "#/components/schemas/ReceiptRef"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "CountRequest": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 1
          },
          "receipt_ref": {
            "$ref": "#/components/schemas/ReceiptRef"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "ReceiptOnlyRequest": {
        "type": "object",
        "properties": {
          "receipt_ref": {
            "$ref": "#/components/schemas/ReceiptRef"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "ActionResult": {
        "type": "object",
        "required": [
          "action",
          "applied",
          "card"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "sale",
              "redeem",
              "balance_spend",
              "balance_topup",
              "membership_renew",
              "pack_sell",
              "pack_use"
            ]
          },
          "applied": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sale only, when applied is false: cooldown, reward_ready, below_min_amount, auto_stamp_off, nothing_to_earn, membership_expired, membership_inactive, not_applicable, program_inactive, subscription_inactive."
          },
          "event": {
            "type": [
              "string",
              "null"
            ]
          },
          "receipt_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "earned_points": {
            "type": "integer"
          },
          "earned_balance": {
            "type": "integer"
          },
          "balance_spent_on_receipt": {
            "type": "integer"
          },
          "redeemed_points": {
            "type": "integer"
          },
          "reward_id": {
            "type": "string"
          },
          "product": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "ref": {
                "type": "string"
              },
              "value": {
                "type": "integer"
              }
            }
          },
          "spent": {
            "type": "integer"
          },
          "credited": {
            "type": "integer"
          },
          "added_items": {
            "type": "integer"
          },
          "used_items": {
            "type": "integer"
          },
          "activated": {
            "type": "boolean"
          },
          "card": {
            "$ref": "#/components/schemas/Card"
          }
        }
      },
      "ReverseResult": {
        "type": "object",
        "properties": {
          "receipt_ref": {
            "type": "string"
          },
          "reversed_actions": {
            "type": "integer"
          },
          "cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Card"
            }
          }
        }
      },
      "Receipt": {
        "type": "object",
        "properties": {
          "receipt_ref": {
            "type": "string"
          },
          "transactions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "type": {
                  "type": "string"
                },
                "card_number": {
                  "type": "string"
                },
                "serial": {
                  "type": "string"
                },
                "amount": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "points": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "balance": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "stamps": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "reward_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reverses_id": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "reversed": {
                  "type": "boolean"
                },
                "till": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "PairingRequest": {
        "type": "object",
        "required": [
          "device_name"
        ],
        "properties": {
          "device_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          },
          "partner_id": {
            "type": "string",
            "pattern": "^pt_[0-9a-f]{24}$",
            "description": "Your partner id from Fidelia (referrals)."
          }
        }
      },
      "Pairing": {
        "type": "object",
        "properties": {
          "pairing_id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "pattern": "^[0-9]{6}$"
          },
          "poll_secret": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PairingResult": {
        "type": "object",
        "properties": {
          "status": {
            "const": "approved"
          },
          "key": {
            "type": "string"
          },
          "client_id": {
            "type": "string"
          },
          "shop": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "country": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable code to branch on."
          },
          "message": {
            "type": "string",
            "description": "English, for logs."
          },
          "detail": {
            "type": "string"
          },
          "field": {
            "type": "string"
          },
          "retry_after_seconds": {
            "type": "integer"
          },
          "request_id": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "E400": {
        "description": "Invalid input (`invalid_input` + `field`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "invalid_input",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "Paramètre invalide (`invalid_input` + `field`)"
      },
      "E401": {
        "description": "Missing, malformed or revoked key (`invalid_key`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "invalid_key",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "Clé absente, mal formée ou révoquée (`invalid_key`)"
      },
      "E402": {
        "description": "The shop needs Pro (`plan_required`) or its subscription ended (`subscription_inactive`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "plan_required",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "Le commerce doit avoir Pro (`plan_required`) ou son abonnement a pris fin (`subscription_inactive`)"
      },
      "E403": {
        "description": "The key may not do this (`scope_denied`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "scope_denied",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "La clé ne peut pas faire cela (`scope_denied`)"
      },
      "E404": {
        "description": "Not found (`card_not_found`, `receipt_not_found`, `pairing_not_found`, `milestone_not_found`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "card_not_found",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "Introuvable (`card_not_found`, `receipt_not_found`, `pairing_not_found`, `milestone_not_found`)"
      },
      "E409": {
        "description": "The card's state refuses it (`insufficient_balance`, `not_ready`, `already_claimed`, `wrong_card_type`, `reverse_conflict`, `idempotency_mismatch`, …)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "insufficient_balance",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "L'état de la carte le refuse (`insufficient_balance`, `not_ready`, `already_claimed`, `wrong_card_type`, `reverse_conflict`, `idempotency_mismatch`, …)"
      },
      "E410": {
        "description": "Gone (`card_voided`, `pairing_expired`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "card_voided",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "N'existe plus (`card_voided`, `pairing_expired`)"
      },
      "E429": {
        "description": "Too many requests (`rate_limited`); wait `Retry-After` seconds",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "rate_limited",
              "message": "…",
              "request_id": "6f1c…"
            }
          }
        },
        "x-description-fr": "Trop de requêtes (`rate_limited`) ; attendez `Retry-After` secondes"
      }
    }
  }
}