FideliaDevelopers · POS APIFrançais

API reference

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/

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

Shop

The shop behind the key.

GET /me

The shop, its locations and cards

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

NameInDescription
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Responses

  • 200 OK
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

Cards

Look up a card and act on it.

GET /cards/{code}

Look up a card

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.

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Responses

  • 200 OK
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /cards/{code}/sale

Record a sale

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

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

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

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

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

Use a reward

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.

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
reward_id · requiredpathready, milestone:<id> or a points reward id
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

{
  "receipt_ref": "T1-000042"
}

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /cards/{code}/balance/spend

Pay with the card balance

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.

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

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

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /cards/{code}/balance/topup

Top up a prepaid card

The customer paid amount at the till; the card is credited amount plus the program's bonus (credited).

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

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

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /cards/{code}/membership/renew

Renew or activate a membership

Extends the membership by the program's validity, or activates a membership waiting for payment.

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

{
  "receipt_ref": "T1-000044"
}

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /cards/{code}/pack/sell

Sell or activate a pack

A pack waiting for payment is activated; an active one gets count more packs.

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

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

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /cards/{code}/pack/use

Use pack items

Takes count items (1–10) from an active pack.

NameInDescription
code · requiredpathSerial (QR, 32 hex) or card number (barcode, 13 digits).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Request body (example)

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

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 410 Gone (card_voided, pairing_expired)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

Receipts

What a receipt did; refunds.

GET /receipts/{receipt_ref}

What a receipt did

Every ledger row of this shop with this receipt number, reversals included.

NameInDescription
receipt_ref · requiredpathThe till's receipt number (unique per shop).

Responses

  • 200 OK
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

POST /receipts/{receipt_ref}/reverse

Refund / cancel a receipt

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.

NameInDescription
receipt_ref · requiredpathThe till's receipt number (unique per shop).
Idempotency-Key · requiredheaderUnique 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.
Fidelia-DeviceheaderTill name shown in the shop's history (default: the key's name).
langqueryLanguage of reward and card names (one of the shop's languages; also read from Accept-Language).

Responses

  • 200 OK
  • 400 Invalid input (invalid_input + field)
  • 401 Missing, malformed or revoked key (invalid_key)
  • 402 The shop needs Pro (plan_required) or its subscription ended (subscription_inactive)
  • 403 The key may not do this (scope_denied)
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 409 The card's state refuses it (insufficient_balance, not_ready, already_claimed, wrong_card_type, reverse_conflict, idempotency_mismatch, …)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

Pairing

Connect a till without typing a key.

POST /pairing

Start pairing · No key

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.

Request body (example)

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

Responses

  • 201 OK
  • 400 Invalid input (invalid_input + field)
  • 429 Too many requests (rate_limited); wait Retry-After seconds

GET /pairing/{pairing_id}

Collect the key · No key

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.

NameInDescription
pairing_id · requiredpath

Responses

  • 200 OK
  • 202 Waiting for the owner
  • 404 Not found (card_not_found, receipt_not_found, pairing_not_found, milestone_not_found)
  • 410 Gone (card_voided, pairing_expired)

Objects

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