{
  "openapi": "3.1.0",
  "info": {
    "title": "Keyring — integration API",
    "version": "2026-08-09",
    "summary": "The endpoints an external point-of-sale, kiosk or third party calls on a portal running Keyring.",
    "description": "Loyalty, gift-card and voucher operations against a HubSpot portal running Keyring.\n\n**The endpoint inventory here is generated** from the app’s own deployment and policy manifests, so it cannot list an endpoint that is not deployed or miss one that is.\n**Request and response shapes are hand-written** and carry a date, not a guarantee — this document is version `2026-08-09`, the day the reference was last read against the code. 7 of 21 paths have documented shapes; the rest are marked `x-shape-documented: false` rather than given an invented one.\n\n**Authentication is a body field, not a header.** A staff token is sent as `staffToken` inside the JSON body, so it appears in the request schemas rather than as an OpenAPI security scheme. Mint one at `keyring-staff-session`, and re-mint on a 401.\n\nFull narrative contract, error handling and the onboarding test plan: https://worksby.design/docs/keyring-integration",
    "contact": {
      "name": "Works by Design",
      "email": "w@reus.ie",
      "url": "https://worksby.design/docs/keyring"
    }
  },
  "servers": [
    {
      "url": "https://{portalDomain}/hs/serverless",
      "description": "The customer portal’s own public domain. Ask the operator — do not assume it matches another environment.",
      "variables": {
        "portalDomain": {
          "default": "www.example.com"
        }
      }
    }
  ],
  "tags": [
    {
      "name": "public",
      "description": "Callable with no credential."
    },
    {
      "name": "staff",
      "description": "Requires a staff token; authority follows the token venue’s capabilities."
    },
    {
      "name": "customer",
      "description": "Identity comes from the customer’s own token, never from the body."
    },
    {
      "name": "retired",
      "description": "Withdrawn. Still answers, with 410, so a cached client gets an explanation."
    }
  ],
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "code": {
            "type": "string",
            "description": "The machine-readable failure. Handle by name; treat an unrecognised value as a generic failure rather than crashing.",
            "examples": [
              "STAFF_TOKEN_INVALID",
              "INSUFFICIENT_BALANCE",
              "ALREADY_REDEEMED",
              "EXPIRED",
              "INVALID_LOCATION",
              "CAPABILITY_MISSING",
              "VOID_NOT_ALLOWED",
              "RATE_LIMITED",
              "IDEMPOTENCY_KEY_REQUIRED",
              "NOT_BOOTSTRAPPED",
              "ENDPOINT_RETIRED"
            ]
          },
          "error": {
            "type": "string",
            "description": "Human-readable. Do not switch on it."
          },
          "currentBalance": {
            "type": "number",
            "description": "On INSUFFICIENT_BALANCE — offer to spend this instead."
          },
          "validLocations": {
            "type": "array",
            "description": "On INVALID_LOCATION — where it would work. Show these; do not retry elsewhere.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "retryAfterMs": {
            "type": "integer",
            "description": "On RATE_LIMITED."
          }
        }
      }
    }
  },
  "paths": {
    "/keyring-fetch-locations": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "List venues and their capabilities",
        "description": "What a till needs to populate a venue selector, and to know what it will be allowed to do at each.\n\n**Auth:** None",
        "operationId": "keyringFetchLocations",
        "x-surface": "public",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "public"
        ],
        "summary": "List venues and their capabilities",
        "description": "What a till needs to populate a venue selector, and to know what it will be allowed to do at each.\n\n**Auth:** None",
        "operationId": "keyringFetchLocations",
        "x-surface": "public",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/keyring-qr-sheet": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "keyring-qr-sheet",
        "description": "**Auth:** None",
        "operationId": "keyringQrSheet",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "public"
        ],
        "summary": "keyring-qr-sheet",
        "description": "**Auth:** None",
        "operationId": "keyringQrSheet",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-reports": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "keyring-reports",
        "description": "**Auth:** None (no personal data)",
        "operationId": "keyringReports",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "public"
        ],
        "summary": "keyring-reports",
        "description": "**Auth:** None (no personal data)",
        "operationId": "keyringReports",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-workflow-options": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "keyring-workflow-options",
        "description": "**Auth:** None (no personal data)",
        "operationId": "keyringWorkflowOptions",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "public"
        ],
        "summary": "keyring-workflow-options",
        "description": "**Auth:** None (no personal data)",
        "operationId": "keyringWorkflowOptions",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-claim-incentive": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "keyring-claim-incentive",
        "description": "**Auth:** None",
        "operationId": "keyringClaimIncentive",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-fetch-incentive": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Read a voucher or gift card",
        "description": "Unauthenticated and cheap. Call it at scan time — balances move, so do not cache across sales.\n\n**Auth:** None",
        "operationId": "keyringFetchIncentive",
        "x-surface": "public",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "incentive": {
                      "type": "object",
                      "properties": {
                        "redemption_type": {
                          "type": "string",
                          "enum": [
                            "balance",
                            "product",
                            "percentage",
                            "entry"
                          ]
                        },
                        "currency": {
                          "type": "string"
                        },
                        "initial_balance": {
                          "type": "number"
                        },
                        "current_balance": {
                          "type": "number"
                        },
                        "display_value": {
                          "type": "string",
                          "description": "Pre-formatted for a cashier-facing screen."
                        },
                        "is_claimed": {
                          "type": "boolean"
                        },
                        "is_claimable": {
                          "type": "boolean",
                          "description": "Independent of is_claimed — a claimed instrument can still be claimable. Do not treat one as the negation of the other."
                        },
                        "status": {
                          "type": "string"
                        },
                        "is_expired": {
                          "type": "boolean"
                        },
                        "is_personal": {
                          "type": "boolean",
                          "description": "Personal instruments are bound to one contact."
                        },
                        "valid_scope": {
                          "type": "string",
                          "enum": [
                            "all",
                            "listed"
                          ]
                        },
                        "where_valid": {
                          "type": "array",
                          "description": "Empty means valid anywhere.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "incentiveId"
                ],
                "properties": {
                  "incentiveId": {
                    "type": "string",
                    "description": "The scanned code. Case-insensitive."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keyring-fetch-product": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "keyring-fetch-product",
        "description": "**Auth:** None",
        "operationId": "keyringFetchProduct",
        "x-surface": "public",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-staff-session": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Sign a till in",
        "description": "Mints the staff token every authenticated call carries. Bound to one contact and one venue; expires (8 hours by default). Re-mint on a 401 rather than treating it as fatal.\n\n**Auth:** None (the PIN is enforced inside when one is configured)",
        "operationId": "keyringStaffSession",
        "x-surface": "public",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "token": {
                      "type": "string",
                      "description": "Send as `staffToken` on every authenticated call. A credential — never log it."
                    },
                    "expiresAt": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Epoch milliseconds."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "locationId"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "The staff contact representing this till. Either this or contactId."
                  },
                  "contactId": {
                    "type": "string"
                  },
                  "locationId": {
                    "type": "string",
                    "description": "The venue record id. Its capabilities are what this token may do."
                  },
                  "pin": {
                    "type": "string",
                    "description": "Required whenever the install has a PIN configured."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keyring-enrollment-create": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "keyring-enrollment-create",
        "description": "**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringEnrollmentCreate",
        "x-surface": "staff",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-enrollment-search": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "keyring-enrollment-search",
        "description": "**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringEnrollmentSearch",
        "x-surface": "staff",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-generate-incentives": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "keyring-generate-incentives",
        "description": "**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringGenerateIncentives",
        "x-surface": "staff",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-print-member-card": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "keyring-print-member-card",
        "description": "**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringPrintMemberCard",
        "x-surface": "staff",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-staff-config": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "What can be issued or awarded here",
        "description": "The catalogue templates and active programmes behind a till’s issue and award forms.\n\n**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringStaffConfig",
        "x-surface": "staff",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "staffToken"
                ],
                "properties": {
                  "staffToken": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keyring-staff-home": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "Today’s activity at this venue",
        "description": "**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringStaffHome",
        "x-surface": "staff",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "staffToken"
                ],
                "properties": {
                  "staffToken": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keyring-transact": {
      "post": {
        "tags": [
          "staff"
        ],
        "summary": "The staff mutation surface — redeem, reload, award, issue, void",
        "description": "Every action requires `idempotencyKey`: one per business operation, persisted before sending and reused on retry. A repeat returns `success: true, duplicate: true` with unchanged state — treat that as success. Authority is re-checked server-side against the token venue’s capabilities.\n\n**Auth:** Staff token + the location capability for the action",
        "operationId": "keyringTransact",
        "x-surface": "staff",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "duplicate": {
                      "type": "boolean",
                      "description": "The key had already been used. The state returned is authoritative."
                    },
                    "transactionId": {
                      "type": "string"
                    },
                    "remainingBalance": {
                      "type": "number"
                    },
                    "membershipId": {
                      "type": "string"
                    },
                    "newBalance": {
                      "type": "number"
                    },
                    "externalSync": {
                      "type": "string",
                      "description": "Present when an integration provider is configured."
                    },
                    "externalSyncMessage": {
                      "type": "string",
                      "description": "The cashier-facing instruction to show, when one applies."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action",
                  "staffToken",
                  "idempotencyKey"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "redeem",
                      "reload",
                      "award",
                      "issue",
                      "void"
                    ]
                  },
                  "staffToken": {
                    "type": "string"
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "description": "One UUID per business operation — NOT per HTTP attempt."
                  },
                  "code": {
                    "type": "string",
                    "description": "redeem: the scanned code."
                  },
                  "recordId": {
                    "type": "string",
                    "description": "redeem/reload: use for a just-created record — code lookup goes through a search index that lags writes."
                  },
                  "amount": {
                    "type": "number",
                    "description": "redeem: part of a balance (omit to spend it all). reload: the top-up. award: the spend, converted by the programme earn rate."
                  },
                  "contactId": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "programId": {
                    "type": "string"
                  },
                  "points": {
                    "type": "number",
                    "description": "award: a literal delta. May be negative for a correction; never zero."
                  },
                  "templateKey": {
                    "type": "string",
                    "description": "issue: from the voucher catalogue."
                  },
                  "transactionId": {
                    "type": "string",
                    "description": "void: the ledger row to reverse."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keyring-fetch-wallet": {
      "post": {
        "tags": [
          "customer"
        ],
        "summary": "keyring-fetch-wallet",
        "description": "**Auth:** Customer token — identity comes from the token",
        "operationId": "keyringFetchWallet",
        "x-surface": "customer",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-offer-decision": {
      "post": {
        "tags": [
          "customer"
        ],
        "summary": "keyring-offer-decision",
        "description": "**Auth:** Customer token — identity comes from the token",
        "operationId": "keyringOfferDecision",
        "x-surface": "customer",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-request-link": {
      "post": {
        "tags": [
          "customer"
        ],
        "summary": "keyring-request-link",
        "description": "**Auth:** Customer token — identity comes from the token",
        "operationId": "keyringRequestLink",
        "x-surface": "customer",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-resolve-token": {
      "post": {
        "tags": [
          "customer"
        ],
        "summary": "Identify a customer from a scanned member card",
        "description": "Input is the `t` parameter from the scanned wallet URL. The response is name-only by design — never an email address or a phone number. Supplying a staff token additionally records the identification against the venue.\n\nNote the casing: this response is camelCase, while `keyring-fetch-incentive` returns the CRM property names in snake_case. The two describe the same instrument.\n\n**Auth:** Customer token — identity comes from the token",
        "operationId": "keyringResolveToken",
        "x-surface": "customer",
        "x-shape-documented": true,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "valid": {
                      "type": "boolean"
                    },
                    "tokenClass": {
                      "type": "string",
                      "enum": [
                        "session",
                        "member",
                        "staff"
                      ]
                    },
                    "expiresAt": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "identified": {
                      "type": "boolean",
                      "description": "Whether an identification was RECORDED against a venue — true only when a staffToken was sent and that venue holds the identify capability. Not a signal of whether the contact was recognised: check `valid` and `contact` for that."
                    },
                    "contact": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "firstName": {
                          "type": "string"
                        },
                        "lastName": {
                          "type": "string"
                        }
                      }
                    },
                    "memberships": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "membershipId": {
                            "type": "string"
                          },
                          "programId": {
                            "type": "string"
                          },
                          "programName": {
                            "type": "string"
                          },
                          "pointsName": {
                            "type": "string"
                          },
                          "accentColor": {
                            "type": "string"
                          },
                          "pointsBalance": {
                            "type": "number"
                          },
                          "tier": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "joinedDate": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date"
                          }
                        }
                      }
                    },
                    "incentives": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "recordId": {
                            "type": "string"
                          },
                          "code": {
                            "type": "string"
                          },
                          "redemptionType": {
                            "type": "string"
                          },
                          "incentiveType": {
                            "type": "string"
                          },
                          "value": {
                            "type": "number"
                          },
                          "currentBalance": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "currency": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "string"
                          },
                          "isExpired": {
                            "type": "boolean"
                          },
                          "engagementStatus": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "claimedAt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "redeemedAt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    },
                    "ledger": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "transactionId": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string"
                          },
                          "pointsDelta": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "valueDelta": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "occurredAt": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "voided": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "pointsEarnedLifetime": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "staffToken": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keyring-user-create": {
      "post": {
        "tags": [
          "retired"
        ],
        "summary": "keyring-user-create",
        "description": "**Auth:** Retired — returns `410 ENDPOINT_RETIRED`",
        "operationId": "keyringUserCreate",
        "deprecated": true,
        "x-surface": "retired",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Retired. Returns `code: \"ENDPOINT_RETIRED\"`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    },
    "/keyring-user-lookup": {
      "post": {
        "tags": [
          "retired"
        ],
        "summary": "keyring-user-lookup",
        "description": "**Auth:** Retired — returns `410 ENDPOINT_RETIRED`",
        "operationId": "keyringUserLookup",
        "deprecated": true,
        "x-surface": "retired",
        "x-shape-documented": false,
        "responses": {
          "200": {
            "description": "Success. Response shape is not documented — see keyring-integration.md.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Retired. Returns `code: \"ENDPOINT_RETIRED\"`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refused. Always carries a structured `code` — switch on that, never on the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Not documented — see keyring-integration.md."
              }
            }
          }
        }
      }
    }
  }
}
