{
  "openapi": "3.1.0",
  "info": {
    "title": "TapCoupon POS and partner API",
    "version": "1.0.0",
    "description": "API-key POS operations, customer card views, and partner payment sessions. Verified against the tap_coupon backend on 2026-10-02. Dashboard /clients and administrative routes use separate authentication and are outside this reference."
  },
  "servers": [
    {
      "url": "https://tapcoupon.at/api/v1"
    }
  ],
  "paths": {
    "/cards": {
      "get": {
        "operationId": "cards_get",
        "summary": "Get card",
        "description": "Retrieve the newest unarchived card instance for your API key's account.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                },
                "example": {
                  "cardId": "9866191586296",
                  "cardType": "loyaltyCard",
                  "activated": true,
                  "lifetime": 0,
                  "scansNeeded": 5,
                  "currentScans": 1,
                  "rewardMessage": "You earned a free coffee!",
                  "amountRenews": 1,
                  "unlimitedRenews": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "cardId",
            "in": "query",
            "required": true,
            "description": "Printed or digital card identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "timestamp",
            "in": "query",
            "required": true,
            "description": "Request timestamp.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/coupon-cards": {
      "post": {
        "operationId": "cards_use_coupon",
        "summary": "Use coupon card",
        "description": "Deduct a positive amount from a coupon card.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "amountLeft": {
                      "type": "integer"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "amountLeft": 4500,
                  "success": true,
                  "message": "$t(amountChargedSuccessfully)"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Card inactive, fully used, or cannot be reconfigured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cardId": {
                    "type": "string"
                  },
                  "sessionToken": {
                    "type": "string",
                    "description": "Optional partner payment-session token."
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Cents to deduct.",
                    "minimum": 1,
                    "maximum": 100000
                  }
                },
                "required": [
                  "cardId",
                  "amount"
                ]
              },
              "example": {
                "cardId": "9866191586296",
                "amount": 500
              }
            }
          }
        }
      }
    },
    "/loyalty-cards": {
      "post": {
        "operationId": "cards_use_loyalty",
        "summary": "Use loyalty card",
        "description": "Record one stamp and report whether the customer earned a reward.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "scansLeft": {
                      "type": "integer"
                    },
                    "renewsLeft": {
                      "type": "integer"
                    },
                    "rewardEarned": {
                      "type": "boolean"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "scansLeft": 5,
                  "renewsLeft": 0,
                  "rewardEarned": true,
                  "success": true,
                  "message": "You earned a free coffee!"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Card inactive, fully used, or cannot be reconfigured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cardId": {
                    "type": "string"
                  },
                  "sessionToken": {
                    "type": "string",
                    "description": "Optional partner payment-session token."
                  }
                },
                "required": [
                  "cardId"
                ]
              },
              "example": {
                "cardId": "9866191586296"
              }
            }
          }
        }
      }
    },
    "/points-cards": {
      "post": {
        "operationId": "cards_use_points",
        "summary": "Add points",
        "description": "Add points to a points card.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "newPoints": {
                      "type": "integer"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "newPoints": 150,
                  "success": true,
                  "message": "$t(pointsCardPointsAddedSuccessfully)"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Card inactive, fully used, or cannot be reconfigured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cardId": {
                    "type": "string"
                  },
                  "sessionToken": {
                    "type": "string",
                    "description": "Optional partner payment-session token."
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Points to add.",
                    "minimum": 1,
                    "maximum": 100000
                  }
                },
                "required": [
                  "cardId",
                  "amount"
                ]
              },
              "example": {
                "cardId": "9866191586296",
                "amount": 100
              }
            }
          }
        }
      }
    },
    "/status-cards": {
      "post": {
        "operationId": "cards_use_status",
        "summary": "Use status card",
        "description": "Award points to a status card and calculate its current tier.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "levelUpgraded": {
                      "type": "boolean"
                    },
                    "newLevel": {
                      "type": "string"
                    },
                    "newPoints": {
                      "type": "integer"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "levelUpgraded": true,
                  "newLevel": "693869428d49e641ba110e18",
                  "newPoints": 50,
                  "success": true,
                  "message": "$t(rewardCardUsedSuccessfully)"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Card inactive, fully used, or cannot be reconfigured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cardId": {
                    "type": "string"
                  },
                  "sessionToken": {
                    "type": "string",
                    "description": "Optional partner payment-session token."
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Points to add.",
                    "minimum": 1,
                    "maximum": 100000
                  }
                },
                "required": [
                  "cardId",
                  "amount"
                ]
              },
              "example": {
                "cardId": "9866191586296",
                "amount": 100
              }
            }
          }
        }
      }
    },
    "/points-card-rewards/redeem": {
      "post": {
        "operationId": "cards_redeem_reward",
        "summary": "Redeem points reward",
        "description": "Spend points on a reward for a points card.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pointsUsed": {
                      "type": "integer"
                    },
                    "newPoints": {
                      "type": "integer"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "pointsUsed": 100,
                  "newPoints": 50,
                  "success": true,
                  "message": "$t(pointsCardRewardRedeemed, {reward: Free coffee})"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reward cooldown is active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Card inactive, fully used, or cannot be reconfigured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cardId": {
                    "type": "string"
                  },
                  "sessionToken": {
                    "type": "string",
                    "description": "Optional partner payment-session token."
                  },
                  "rewardId": {
                    "type": "string",
                    "description": "MongoDB object id.",
                    "pattern": "^[a-fA-F0-9]{24}$"
                  }
                },
                "required": [
                  "cardId",
                  "rewardId"
                ]
              },
              "example": {
                "cardId": "9866191586296",
                "rewardId": "693869428d49e641ba110e18"
              }
            }
          }
        }
      }
    },
    "/cards/apply": {
      "post": {
        "operationId": "cards_apply_template",
        "summary": "Apply template",
        "description": "Activate a blank card or reconfigure an eligible existing card.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "message": "Template applied to card successfully"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Card inactive, fully used, or cannot be reconfigured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cardId": {
                    "type": "string"
                  },
                  "templateId": {
                    "type": "string",
                    "description": "MongoDB object id.",
                    "pattern": "^[a-fA-F0-9]{24}$"
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Positive coupon amount in cents. Required for variable-value coupon templates.",
                    "minimum": 1
                  },
                  "testCard": {
                    "type": "boolean",
                    "description": "Request a test card, subject to the account test period and card limit."
                  },
                  "customer": {
                    "$ref": "#/components/schemas/CustomerInput"
                  },
                  "cancelPrevious": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "cardId",
                  "templateId"
                ]
              },
              "example": {
                "cardId": "9866191586296",
                "templateId": "693869428d49e641ba110e17",
                "customer": {
                  "email": "bob@example.com",
                  "contact": {
                    "fullName": "Bob Example",
                    "phone": "+43123456789"
                  },
                  "address": {
                    "line1": "Bakers Street 41",
                    "city": "Vienna",
                    "postalCode": "1010",
                    "countryCode": "AT"
                  },
                  "birthdate": "1990-06-15T00:00:00Z",
                  "privacyConsentGiven": true
                }
              }
            }
          }
        }
      }
    },
    "/cards/create": {
      "post": {
        "operationId": "cards_create_digital",
        "summary": "Create digital card",
        "description": "Issue a card from a template and email its QR code to the customer.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                },
                "example": {
                  "cardId": "2945823033425",
                  "cardType": "couponCard",
                  "activated": true,
                  "amount": 5000,
                  "amountLeft": 5000,
                  "customerEmail": "bob@example.com",
                  "testCard": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Server, subscription eligibility, test-window, or test-card-limit failure. Issuance currently wraps these repository failures as 500 errors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "templateId": {
                    "type": "string",
                    "description": "MongoDB object id.",
                    "pattern": "^[a-fA-F0-9]{24}$"
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Positive coupon amount in cents. Required for variable-value coupon templates.",
                    "minimum": 1
                  },
                  "testCard": {
                    "type": "boolean",
                    "description": "Request a test card, subject to the account test period and card limit."
                  },
                  "customer": {
                    "$ref": "#/components/schemas/CustomerInput"
                  }
                },
                "required": [
                  "templateId",
                  "customer"
                ]
              },
              "example": {
                "templateId": "693869428d49e641ba110e17",
                "customer": {
                  "email": "bob@example.com",
                  "contact": {
                    "fullName": "Bob Example",
                    "phone": "+43123456789"
                  },
                  "address": {
                    "line1": "Bakers Street 41",
                    "city": "Vienna",
                    "postalCode": "1010",
                    "countryCode": "AT"
                  },
                  "birthdate": "1990-06-15T00:00:00Z",
                  "privacyConsentGiven": true
                }
              }
            }
          }
        }
      }
    },
    "/card/{cardId}": {
      "get": {
        "operationId": "cards_public_get",
        "summary": "Public card lookup",
        "description": "Retrieve public card history without an API key.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Card"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [],
        "parameters": [
          {
            "name": "cardId",
            "in": "path",
            "required": true,
            "description": "Printed or digital identifier.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/card/{cardId}/rewards": {
      "get": {
        "operationId": "cards_public_rewards",
        "summary": "Public card rewards",
        "description": "Retrieve configured points rewards and cooldown availability for each card instance.",
        "tags": [
          "Cards"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicCardRewards"
                  }
                },
                "example": [
                  {
                    "id": "693869428d49e641ba110e17",
                    "cardId": "9866191586296",
                    "rewards": [
                      {
                        "id": "693869428d49e641ba110e18",
                        "name": "Free coffee",
                        "description": "One coffee",
                        "pointsNeeded": 100,
                        "cooldownHours": 24,
                        "availableAt": "2026-10-03T12:00:00Z"
                      }
                    ]
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [],
        "parameters": [
          {
            "name": "cardId",
            "in": "path",
            "required": true,
            "description": "Printed or digital identifier.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/templates": {
      "get": {
        "operationId": "templates_list",
        "summary": "List templates",
        "description": "Retrieve templates for your API key's account.",
        "tags": [
          "Templates"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "templates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Template"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/employee/login": {
      "post": {
        "operationId": "employees_login",
        "summary": "Employee login",
        "description": "Validate employee credentials and create a session token.",
        "tags": [
          "Employees"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeLogin"
                },
                "example": {
                  "employee": {
                    "id": "693869428d49e641ba110e17",
                    "username": "Cashier",
                    "isAdmin": false,
                    "accessHistory": false,
                    "deleteScanHistory": false,
                    "locationIds": [
                      "693869428d49e641ba110e18"
                    ],
                    "clientId": "693869428d49e641ba110e19",
                    "activeUserAgentName": ""
                  },
                  "message": "Login successful",
                  "token": "YOUR_EMPLOYEE_TOKEN",
                  "sessionExpiresAt": "2026-10-03T12:00:00Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Credentials"
              },
              "example": {
                "username": "Cashier",
                "password": "YOUR_EMPLOYEE_PASSWORD"
              }
            }
          }
        }
      }
    },
    "/employees/validate": {
      "post": {
        "operationId": "employees_validate",
        "summary": "Validate employee credentials",
        "description": "Check an employee's credentials and location access without creating a session.",
        "tags": [
          "Employees"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "employee": {
                      "$ref": "#/components/schemas/Employee"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "employee": {
                    "id": "693869428d49e641ba110e17",
                    "username": "Cashier",
                    "isAdmin": false,
                    "accessHistory": false,
                    "deleteScanHistory": false,
                    "locationIds": [
                      "693869428d49e641ba110e18"
                    ],
                    "clientId": "693869428d49e641ba110e19",
                    "activeUserAgentName": ""
                  },
                  "message": "Employee validated successfully"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Credentials"
              },
              "example": {
                "username": "Cashier",
                "password": "YOUR_EMPLOYEE_PASSWORD"
              }
            }
          }
        }
      }
    },
    "/employee/logout": {
      "post": {
        "operationId": "employees_logout",
        "summary": "Employee logout",
        "description": "Invalidate the employee session identified by X-Employee-Token.",
        "tags": [
          "Employees"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Logout successful"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/prices": {
      "get": {
        "operationId": "other_prices",
        "summary": "Prices",
        "description": "Retrieve coupon and loyalty operation pricing for your account.",
        "tags": [
          "Other"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "loyaltyCardSetUpFee": {
                      "type": "integer"
                    },
                    "percentagePerCouponLoad": {
                      "type": "number"
                    }
                  }
                },
                "example": {
                  "loyaltyCardSetUpFee": 25,
                  "percentagePerCouponLoad": 0.01
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/location": {
      "get": {
        "operationId": "other_location",
        "summary": "Location",
        "description": "Retrieve the name of the location associated with your API key.",
        "tags": [
          "Other"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Vienna"
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/customers": {
      "get": {
        "operationId": "other_customers",
        "summary": "Customers",
        "description": "Retrieve customers for your API key's account.",
        "tags": [
          "Other"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Customer"
                  }
                },
                "example": [
                  {
                    "email": "bob@example.com",
                    "contact": {
                      "fullName": "Bob Example",
                      "phone": "+43123456789"
                    },
                    "address": {
                      "line1": "Bakers Street 41",
                      "city": "Vienna",
                      "postalCode": "1010",
                      "countryCode": "AT"
                    },
                    "birthdate": "1990-06-15T00:00:00Z",
                    "privacyConsentGiven": true,
                    "createdAt": "2026-10-02T12:00:00Z",
                    "updatedAt": "2026-10-02T12:00:00Z",
                    "stats": {
                      "totalLoyaltyBonusEarned": 0,
                      "totalLoyaltyBonusUsed": 0,
                      "totalCouponsPurchased": 1,
                      "totalCouponsRedeemed": 0
                    }
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/client-config": {
      "get": {
        "operationId": "other_client_config",
        "summary": "Client configuration",
        "description": "Retrieve mandatory customer fields, test-period status, and subscription availability.",
        "tags": [
          "Other"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientConfig"
                },
                "example": {
                  "mandatoryCustomerFields": {
                    "email": true,
                    "name": true,
                    "phone": false,
                    "address": false,
                    "birthdate": false
                  },
                  "testPeriodStatus": {
                    "inTestWindow": true,
                    "testCardsUsed": 2,
                    "maxTestCards": 10,
                    "testPeriodExpiresAt": "2026-10-15T12:00:00Z"
                  },
                  "hasSubscription": false
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/rewards": {
      "get": {
        "operationId": "other_rewards",
        "summary": "List rewards",
        "description": "Retrieve the account's points reward catalog.",
        "tags": [
          "Other"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Reward"
                  }
                },
                "example": [
                  {
                    "id": "693869428d49e641ba110e18",
                    "name": "Free coffee",
                    "description": "One coffee",
                    "pointsNeeded": 100,
                    "cooldownHours": 24
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/points-card-rewards": {
      "post": {
        "operationId": "other_list_rewards",
        "summary": "List rewards with employee authentication",
        "description": "Retrieve the account's points reward catalog.",
        "tags": [
          "Other"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Reward"
                  }
                },
                "example": [
                  {
                    "id": "693869428d49e641ba110e18",
                    "name": "Free coffee",
                    "description": "One coffee",
                    "pointsNeeded": 100,
                    "cooldownHours": 24
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": [],
            "employeeToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/payment-sessions": {
      "post": {
        "operationId": "integration_create_session",
        "summary": "Create payment session",
        "description": "Create a partner session with a 32-character token and a five-minute completion window.",
        "tags": [
          "Integration"
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "minLength": 32,
                      "maxLength": 32
                    }
                  },
                  "required": [
                    "token"
                  ]
                },
                "example": {
                  "token": "a3f9kqzm8bnwvrtd2xeljcuy4osp1gh7"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, card type, balance, points, or location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionCreate"
              },
              "example": {
                "appName": "Your POS",
                "callbackUrl": "yourpos://payment-complete",
                "supportedCardTypes": [
                  "coupon",
                  "loyalty",
                  "status",
                  "points"
                ]
              }
            }
          }
        }
      }
    },
    "/payment-sessions/{token}": {
      "get": {
        "operationId": "integration_get_status",
        "summary": "Get session status",
        "description": "Read the result of a partner payment session after the callback.",
        "tags": [
          "Integration"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionStatus"
                },
                "example": {
                  "status": "completed",
                  "cardType": "coupon",
                  "amount": 1500,
                  "completedAt": "2026-10-02T12:03:00Z",
                  "expiresAt": "2026-10-02T12:05:00Z"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "Secret session token.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/payment-sessions/{token}/info": {
      "get": {
        "operationId": "integration_get_info",
        "summary": "Get session info",
        "description": "Retrieve partner session metadata for the TapCoupon app.",
        "tags": [
          "Integration"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionInfo"
                },
                "example": {
                  "appName": "Your POS",
                  "callbackUrl": "yourpos://payment-complete",
                  "supportedCardTypes": [
                    "coupon",
                    "loyalty",
                    "status",
                    "points"
                  ],
                  "status": "pending",
                  "expiresAt": "2026-10-02T12:05:00Z"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, device id, employee token, or credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Device binding mismatch, archived location, employee location restriction, or account restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Card archived or payment session unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. The response body is determined by the rate-limit middleware."
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "Secret session token.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Device-ID",
            "in": "header",
            "required": true,
            "description": "Stable device identifier. API key binds to this value on first use.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Location API key. Also requires X-Device-ID."
      },
      "employeeToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Employee-Token",
        "description": "Employee session token from /employee/login."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error."
          },
          "errorCode": {
            "type": "string",
            "description": "Handler errors include a machine-readable code; middleware errors may omit it."
          }
        },
        "required": [
          "error"
        ]
      },
      "Contact": {
        "type": "object",
        "properties": {
          "fullName": {
            "type": "string",
            "maxLength": 200
          },
          "displayName": {
            "type": "string",
            "maxLength": 100
          },
          "company": {
            "type": "string",
            "maxLength": 100
          },
          "phone": {
            "type": "string",
            "maxLength": 20
          }
        },
        "required": [
          "fullName"
        ]
      },
      "Address": {
        "type": "object",
        "properties": {
          "line1": {
            "type": "string",
            "maxLength": 255
          },
          "line2": {
            "type": "string",
            "maxLength": 255
          },
          "city": {
            "type": "string",
            "maxLength": 100
          },
          "state": {
            "type": "string",
            "maxLength": 100
          },
          "postalCode": {
            "type": "string",
            "maxLength": 20
          },
          "countryCode": {
            "type": "string",
            "description": "Two-letter country code.",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "required": [
          "line1",
          "city",
          "postalCode",
          "countryCode"
        ]
      },
      "CustomerInput": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255
          },
          "contact": {
            "$ref": "#/components/schemas/Contact"
          },
          "address": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Address"
              },
              {
                "type": "null"
              }
            ]
          },
          "birthdate": {
            "type": "string",
            "format": "date-time"
          },
          "privacyConsentGiven": {
            "type": "boolean"
          }
        },
        "required": [
          "email",
          "contact"
        ]
      },
      "Customer": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerInput"
          },
          {
            "type": "object",
            "properties": {
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              },
              "stats": {
                "type": "object",
                "properties": {
                  "totalLoyaltyBonusEarned": {
                    "type": "integer"
                  },
                  "totalLoyaltyBonusUsed": {
                    "type": "integer"
                  },
                  "totalCouponsPurchased": {
                    "type": "integer"
                  },
                  "totalCouponsRedeemed": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        ]
      },
      "Employee": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "username": {
            "type": "string"
          },
          "isAdmin": {
            "type": "boolean"
          },
          "accessHistory": {
            "type": "boolean"
          },
          "deleteScanHistory": {
            "type": "boolean"
          },
          "locationIds": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "MongoDB object id.",
              "pattern": "^[a-fA-F0-9]{24}$"
            }
          },
          "clientId": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "activeUserAgentName": {
            "type": "string"
          }
        }
      },
      "Credentials": {
        "type": "object",
        "properties": {
          "username": {
            "type": "string"
          },
          "password": {
            "type": "string"
          }
        },
        "required": [
          "username",
          "password"
        ]
      },
      "EmployeeLogin": {
        "type": "object",
        "properties": {
          "employee": {
            "$ref": "#/components/schemas/Employee"
          },
          "token": {
            "type": "string"
          },
          "sessionExpiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "employee",
          "token",
          "sessionExpiresAt",
          "message"
        ]
      },
      "Location": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "pocketId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "archived": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "StatusLevel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "points": {
            "type": "integer",
            "description": "Cost to advance from this level to the next.",
            "minimum": 0
          },
          "color": {
            "type": "string"
          }
        }
      },
      "StatusProgram": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "pocketId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "durationDays": {
            "type": "integer",
            "description": "Rolling points window in days.",
            "minimum": 1,
            "maximum": 36500
          },
          "levels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StatusLevel"
            }
          },
          "archived": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PointsProgram": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "pocketId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "rewardIds": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "MongoDB object id.",
              "pattern": "^[a-fA-F0-9]{24}$"
            }
          },
          "archived": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BaseCard": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "cardId": {
            "type": "string",
            "description": "Reusable printed or digital card identifier."
          },
          "serialNumber": {
            "type": "integer"
          },
          "pocketId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "activatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "canceledAt": {
            "type": "string",
            "format": "date-time"
          },
          "activated": {
            "type": "boolean"
          },
          "lifetime": {
            "type": "integer",
            "description": "Lifetime in hours, measured from createdAt; 0 means unlimited.",
            "minimum": 0
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "description": "Original coupon value in cents, or template amount for other types."
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "createdByEmployeeId": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "archived": {
            "type": "boolean"
          },
          "testCard": {
            "type": "boolean"
          },
          "isLocationBound": {
            "type": "boolean",
            "description": "When true, use is restricted to the issuing location."
          },
          "customerEmail": {
            "type": "string"
          },
          "templateId": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "logs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "cardId": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "method": {
                  "type": "string"
                },
                "statusCode": {
                  "type": "integer"
                },
                "employeeUsername": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BlankCard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseCard"
          },
          {
            "type": "object",
            "properties": {
              "cardType": {
                "type": "string",
                "enum": [
                  "blankCard"
                ]
              }
            },
            "required": [
              "cardType"
            ]
          }
        ]
      },
      "CouponCard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseCard"
          },
          {
            "type": "object",
            "properties": {
              "cardType": {
                "type": "string",
                "enum": [
                  "couponCard"
                ]
              },
              "amountLeft": {
                "type": "integer",
                "description": "Remaining balance in cents."
              }
            },
            "required": [
              "cardType"
            ]
          }
        ]
      },
      "LoyaltyCard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseCard"
          },
          {
            "type": "object",
            "properties": {
              "cardType": {
                "type": "string",
                "enum": [
                  "loyaltyCard"
                ]
              },
              "scansNeeded": {
                "type": "integer"
              },
              "currentScans": {
                "type": "integer"
              },
              "rewardMessage": {
                "type": "string"
              },
              "amountRenews": {
                "type": "integer"
              },
              "originalAmountRenews": {
                "type": "integer"
              },
              "unlimitedRenews": {
                "type": "boolean"
              }
            },
            "required": [
              "cardType"
            ]
          }
        ]
      },
      "StatusCard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseCard"
          },
          {
            "type": "object",
            "properties": {
              "cardType": {
                "type": "string",
                "enum": [
                  "statusCard"
                ]
              },
              "_": {
                "type": "string",
                "description": "MongoDB object id.",
                "pattern": "^[a-fA-F0-9]{24}$"
              },
              "program": {
                "$ref": "#/components/schemas/StatusProgram"
              },
              "level": {
                "type": "string",
                "description": "MongoDB object id.",
                "pattern": "^[a-fA-F0-9]{24}$"
              },
              "points": {
                "type": "integer",
                "description": "Progress within the current tier; not the lifetime total."
              }
            },
            "required": [
              "cardType"
            ]
          }
        ]
      },
      "PointsCard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseCard"
          },
          {
            "type": "object",
            "properties": {
              "cardType": {
                "type": "string",
                "enum": [
                  "pointsCard"
                ]
              },
              "_": {
                "type": "string",
                "description": "MongoDB object id.",
                "pattern": "^[a-fA-F0-9]{24}$"
              },
              "program": {
                "$ref": "#/components/schemas/PointsProgram"
              },
              "points": {
                "type": "integer",
                "description": "Spendable reward balance."
              }
            },
            "required": [
              "cardType"
            ]
          }
        ]
      },
      "Card": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/BlankCard"
          },
          {
            "$ref": "#/components/schemas/CouponCard"
          },
          {
            "$ref": "#/components/schemas/LoyaltyCard"
          },
          {
            "$ref": "#/components/schemas/StatusCard"
          },
          {
            "$ref": "#/components/schemas/PointsCard"
          }
        ],
        "discriminator": {
          "propertyName": "cardType",
          "mapping": {
            "blankCard": "#/components/schemas/BlankCard",
            "couponCard": "#/components/schemas/CouponCard",
            "loyaltyCard": "#/components/schemas/LoyaltyCard",
            "statusCard": "#/components/schemas/StatusCard",
            "pointsCard": "#/components/schemas/PointsCard"
          }
        }
      },
      "Template": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "pocketId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "templateType": {
            "type": "string",
            "enum": [
              "couponTemplate",
              "loyaltyTemplate",
              "statusTemplate",
              "pointsTemplate"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "isLocationBound": {
            "type": "boolean"
          },
          "isAmountSpecified": {
            "type": "boolean",
            "description": "Coupon templates only. False requires amount at issuance; true uses the fixed template amount."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cardData": {
            "$ref": "#/components/schemas/Card"
          }
        }
      },
      "Reward": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "pocketId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "pointsNeeded": {
            "type": "integer",
            "minimum": 1
          },
          "cooldownHours": {
            "type": "integer",
            "description": "Cooldown per card and reward; 0 disables it.",
            "minimum": 0,
            "maximum": 8760
          }
        }
      },
      "PublicReward": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "pointsNeeded": {
            "type": "integer",
            "minimum": 1
          },
          "cooldownHours": {
            "type": "integer",
            "description": "Cooldown per card and reward; 0 disables it.",
            "minimum": 0,
            "maximum": 8760
          },
          "availableAt": {
            "type": "string",
            "format": "date-time",
            "description": "Omitted when no previous redemption or no cooldown applies."
          }
        }
      },
      "PublicCardRewards": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MongoDB object id.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "cardId": {
            "type": "string"
          },
          "rewards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicReward"
            }
          }
        },
        "required": [
          "id",
          "cardId",
          "rewards"
        ]
      },
      "SessionCreate": {
        "type": "object",
        "properties": {
          "appName": {
            "type": "string",
            "maxLength": 100
          },
          "callbackUrl": {
            "type": "string",
            "maxLength": 500
          },
          "supportedCardTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "coupon",
                "loyalty",
                "status",
                "points"
              ]
            },
            "minItems": 1
          }
        },
        "required": [
          "appName",
          "callbackUrl",
          "supportedCardTypes"
        ]
      },
      "SessionStatus": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "cardType": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "coupon",
                  "loyalty",
                  "status",
                  "points"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "amount": {
            "anyOf": [
              {
                "type": "integer",
                "description": "Coupon deduction in cents; null for other types."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "status",
          "expiresAt",
          "completedAt",
          "cardType",
          "amount"
        ]
      },
      "SessionInfo": {
        "type": "object",
        "properties": {
          "appName": {
            "type": "string"
          },
          "callbackUrl": {
            "type": "string"
          },
          "supportedCardTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "coupon",
                "loyalty",
                "status",
                "points"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClientConfig": {
        "type": "object",
        "properties": {
          "mandatoryCustomerFields": {
            "type": "object",
            "properties": {
              "email": {
                "type": "boolean"
              },
              "name": {
                "type": "boolean"
              },
              "phone": {
                "type": "boolean"
              },
              "address": {
                "type": "boolean"
              },
              "birthdate": {
                "type": "boolean"
              }
            }
          },
          "testPeriodStatus": {
            "type": "object",
            "properties": {
              "inTestWindow": {
                "type": "boolean"
              },
              "testCardsUsed": {
                "type": "integer"
              },
              "maxTestCards": {
                "type": "integer"
              },
              "testPeriodExpiresAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "hasSubscription": {
            "type": "boolean"
          }
        }
      }
    }
  }
}
