FideliaDéveloppeurs · API caisseEnglish

Référence de l’API

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/

Base URL: https://api.fidelia.se/pos/v1 · openapi.json

Shop

Le commerce lié à la clé.

GET /me

Le commerce, ses établissements et ses cartes

À 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).

NomOùDescription
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Réponses

  • 200 OK
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

Cards

Lire une carte et agir dessus.

GET /cards/{code}

Lire une carte

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.

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Réponses

  • 200 OK
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/sale

Enregistrer une vente

À 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).

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "amount": 1250,
  "receipt_ref": "T1-000042"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/rewards/{reward_id}/redeem

Utiliser une récompense

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.

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
reward_id · obligatoirepathready, milestone:<id> ou l'id d'une récompense à points
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "receipt_ref": "T1-000042"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/balance/spend

Payer avec le solde de la carte

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.

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "amount": "all",
  "receipt_total": 1250,
  "receipt_ref": "T1-000042"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/balance/topup

Recharger une carte prépayée

Le client a payé amount en caisse ; la carte est créditée de amount plus le bonus du programme (credited).

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "amount": 2000,
  "receipt_ref": "T1-000043"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/membership/renew

Renouveler ou activer un abonnement

Prolonge l'abonnement de la durée du programme, ou active un abonnement en attente de paiement.

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "receipt_ref": "T1-000044"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/pack/sell

Vendre ou activer un carnet

Un carnet en attente de paiement est activé ; un carnet actif reçoit count carnets de plus.

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "count": 1,
  "receipt_ref": "T1-000045"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /cards/{code}/pack/use

Utiliser des articles du carnet

Retire count articles (1–10) d'un carnet actif.

NomOùDescription
code · obligatoirepathNuméro de série (QR, 32 hexadécimaux) ou numéro de carte (code-barres, 13 chiffres).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Corps de la requête (exemple)

{
  "count": 1,
  "receipt_ref": "T1-000046"
}

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 N'existe plus (card_voided, pairing_expired)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

Receipts

Ce qu'un ticket a fait ; remboursements.

GET /receipts/{receipt_ref}

Ce qu'un ticket a fait

Toutes les lignes du commerce avec ce numéro de ticket, annulations comprises.

NomOùDescription
receipt_ref · obligatoirepathLe numéro de ticket de la caisse (unique par commerce).

Réponses

  • 200 OK
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

POST /receipts/{receipt_ref}/reverse

Rembourser / annuler un ticket

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.

NomOùDescription
receipt_ref · obligatoirepathLe numéro de ticket de la caisse (unique par commerce).
Idempotency-Key · obligatoireheaderUnique 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.
Fidelia-DeviceheaderNom de la caisse affiché dans l'historique (par défaut : le nom de la clé).
langqueryLangue des noms de récompenses et de cartes (une des langues du commerce ; aussi lue dans Accept-Language).

Réponses

  • 200 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 401 Clé absente, mal formée ou révoquée (invalid_key)
  • 402 Le commerce doit avoir Pro (plan_required) ou son abonnement a pris fin (subscription_inactive)
  • 403 La clé ne peut pas faire cela (scope_denied)
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 L'état de la carte le refuse (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

Pairing

Connecter une caisse sans taper de clé.

POST /pairing

Démarrer le jumelage · Sans clé

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.

Corps de la requête (exemple)

{
  "device_name": "Kassa 1",
  "partner_id": "pt_0123456789abcdef01234567"
}

Réponses

  • 201 OK
  • 400 Paramètre invalide (invalid_input + field)
  • 429 Trop de requêtes (rate_limited) ; attendez Retry-After secondes

GET /pairing/{pairing_id}

Récupérer la clé · Sans clé

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

NomOùDescription
pairing_id · obligatoirepath

Réponses

  • 200 OK
  • 202 En attente du commerçant
  • 404 Introuvable (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 410 N'existe plus (card_voided, pairing_expired)

Objets

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"
          ]
        }
      }
    }
  }
}
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"
    }
  }
}
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"
    }
  }
}