{
  "openapi": "3.1.0",
  "info": {
    "title": "Valet by Storey — Public API",
    "version": "1.0.0",
    "description": "Take terminal card payments from your own point-of-sale software. Human docs: https://storeypos.com/valet/docs. LLM reference: https://storeypos.com/llms-full.txt. All example values are fake."
  },
  "servers": [
    {
      "url": "https://storeypos.com"
    }
  ],
  "security": [
    {
      "ValetKey": [],
      "ValetTag": []
    }
  ],
  "paths": {
    "/api/public/valet/sale": {
      "post": {
        "operationId": "createSale",
        "summary": "Create a sale",
        "description": "Runs a card sale on the terminal behind the Valet Tag. referenceId is your idempotency key: repeating it returns the original result with replayed: true instead of charging twice. subtotal is required; taxAmount, tipAmount and shippingAmount default to 0; total must equal their sum (checked to the cent). Terminal stations only — a key-entry Valet Tag is rejected with 400 and must use POST /api/public/valet/session. A card number can never be sent over this API. A decline is still HTTP 200 with status \"declined\" and the processor's reason in error.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaleRequest"
              },
              "example": {
                "referenceId": "order-10482",
                "subtotal": 40,
                "taxAmount": 2.5,
                "total": 42.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sale processed (approved, declined, no_connection or error). Check status, not the HTTP code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SaleResponse"
                },
                "examples": {
                  "approved": {
                    "value": {
                      "storeyTransactionId": "vlt_9f2c8e1a4b3d",
                      "referenceId": "order-10482",
                      "status": "approved",
                      "authCode": "0F3C21",
                      "rrn": "042619005831",
                      "card": {
                        "brand": "visa",
                        "last4": "4242",
                        "entryMode": "contactless"
                      },
                      "amounts": {
                        "subtotal": 40,
                        "taxAmount": 2.5,
                        "tipAmount": 0,
                        "shippingAmount": 0,
                        "total": 42.5
                      },
                      "refundedAmount": 0,
                      "voidedAt": null,
                      "receipt": {
                        "batchNo": "12",
                        "tranNo": "VC07",
                        "responseText": "SUCCESS",
                        "surchargeAmount": "0.00",
                        "cashDiscount": "0.00",
                        "cardholderName": "JANE SAMPLE",
                        "terminal": "2319993484"
                      },
                      "error": null,
                      "declineCategory": null,
                      "channel": "terminal",
                      "capturedAt": "2026-09-16T18:22:04Z",
                      "createdAt": "2026-09-16T18:21:58Z",
                      "replayed": false
                    }
                  },
                  "declined": {
                    "value": {
                      "storeyTransactionId": "vlt_2b7e40c19d55",
                      "referenceId": "order-10483",
                      "status": "declined",
                      "authCode": null,
                      "rrn": "042619005831",
                      "card": {
                        "brand": "visa",
                        "last4": "4242",
                        "entryMode": "contactless"
                      },
                      "amounts": {
                        "subtotal": 40,
                        "taxAmount": 2.5,
                        "tipAmount": 0,
                        "shippingAmount": 0,
                        "total": 42.5
                      },
                      "refundedAmount": 0,
                      "voidedAt": null,
                      "receipt": null,
                      "error": "51 - INSUFFICIENT FUNDS",
                      "declineCategory": "insufficient_funds",
                      "channel": "terminal",
                      "capturedAt": null,
                      "createdAt": "2026-09-16T18:21:58Z",
                      "replayed": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, totals do not add up, or the Valet Tag is a key-entry station.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid X-Valet-Key / X-Valet-Tag.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Body: {\"error\":\"Too many requests. Slow down.\"}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          }
        }
      }
    },
    "/api/public/valet/sale/{storeyTransactionId}/refund": {
      "post": {
        "operationId": "refundSale",
        "summary": "Refund a sale",
        "description": "Refunds an approved sale on the same terminal that took it. Send amount for a partial refund, or omit the body to refund everything remaining. A refund beyond what remains is rejected before anything is sent to the terminal. Refunds can be disabled per location (403). The response carries refundedTotal and remaining.",
        "parameters": [
          {
            "name": "storeyTransactionId",
            "in": "path",
            "required": true,
            "description": "The Valet Ticket returned by the sale (storeyTransactionId).",
            "schema": {
              "type": "string"
            },
            "example": "vlt_9f2c8e1a4b3d"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundRequest"
              },
              "example": {
                "amount": 42.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refund processed. Check status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReversalResponse"
                },
                "example": {
                  "storeyTransactionId": "vlt_9f2c8e1a4b3d",
                  "referenceId": "order-10482",
                  "reversalId": "rvs_5d71b09c2e44",
                  "kind": "refund",
                  "amount": 42.5,
                  "status": "approved",
                  "authCode": "1A7D04",
                  "rrn": "042619005831",
                  "error": null,
                  "declineCategory": null,
                  "channel": "terminal",
                  "refundedTotal": 42.5,
                  "remaining": 0,
                  "createdAt": "2026-09-17T09:14:31Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid amount, sale not approved, already voided or fully refunded, or amount exceeds remaining.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid X-Valet-Key / X-Valet-Tag.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "403": {
            "description": "Refunds are not enabled for this location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "404": {
            "description": "No such Valet Ticket for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "409": {
            "description": "The original sale has no terminal or transaction number on record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Body: {\"error\":\"Too many requests. Slow down.\"}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          }
        }
      }
    },
    "/api/public/valet/sale/{storeyTransactionId}/void": {
      "post": {
        "operationId": "voidSale",
        "summary": "Void a sale",
        "description": "No body. Cancels an approved sale the same day, before settlement. A settled or partially refunded sale cannot be voided — refund it instead (400).",
        "parameters": [
          {
            "name": "storeyTransactionId",
            "in": "path",
            "required": true,
            "description": "The Valet Ticket returned by the sale (storeyTransactionId).",
            "schema": {
              "type": "string"
            },
            "example": "vlt_9f2c8e1a4b3d"
          }
        ],
        "responses": {
          "200": {
            "description": "Void processed. Check status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReversalResponse"
                },
                "example": {
                  "storeyTransactionId": "vlt_9f2c8e1a4b3d",
                  "referenceId": "order-10482",
                  "reversalId": "rvs_8c20e5f71a93",
                  "kind": "void",
                  "amount": 42.5,
                  "status": "approved",
                  "authCode": null,
                  "rrn": "042619005831",
                  "error": null,
                  "declineCategory": null,
                  "channel": "terminal",
                  "refundedTotal": 0,
                  "remaining": 0,
                  "createdAt": "2026-09-16T18:26:12Z"
                }
              }
            }
          },
          "400": {
            "description": "Sale not approved, already voided, partly refunded, or settled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid X-Valet-Key / X-Valet-Tag.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "404": {
            "description": "No such Valet Ticket for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "409": {
            "description": "The original sale has no terminal or transaction number on record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Body: {\"error\":\"Too many requests. Slow down.\"}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No such Valet Ticket for this account."
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ValetKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Valet-Key",
        "description": "Valet Key — per-location API key. Keep server-side."
      },
      "ValetTag": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Valet-Tag",
        "description": "Valet Tag — per-station device ID (terminal or key-entry)."
      }
    },
    "schemas": {
      "SaleRequest": {
        "type": "object",
        "required": [
          "referenceId",
          "subtotal",
          "total"
        ],
        "properties": {
          "referenceId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Your idempotency key."
          },
          "subtotal": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000
          },
          "taxAmount": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0
          },
          "tipAmount": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0
          },
          "shippingAmount": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0
          },
          "total": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Must equal subtotal + taxAmount + tipAmount + shippingAmount."
          },
          "cashDiscountPercent": {
            "type": "number",
            "minimum": 0,
            "maximum": 25,
            "description": "Reserved; accepted but not yet applied."
          },
          "saveCard": {
            "type": "boolean",
            "description": "Keep the card on file after an approved sale. Requires customerRef."
          },
          "customerRef": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          }
        }
      },
      "Card": {
        "type": "object",
        "properties": {
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "last4": {
            "type": [
              "string",
              "null"
            ]
          },
          "entryMode": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Amounts": {
        "type": "object",
        "properties": {
          "subtotal": {
            "type": "number"
          },
          "taxAmount": {
            "type": "number"
          },
          "tipAmount": {
            "type": "number"
          },
          "shippingAmount": {
            "type": "number"
          },
          "total": {
            "type": "number"
          }
        }
      },
      "Receipt": {
        "type": [
          "object",
          "null"
        ],
        "description": "Processor receipt fields; null unless approved.",
        "properties": {
          "batchNo": {
            "type": [
              "string",
              "null"
            ]
          },
          "tranNo": {
            "type": [
              "string",
              "null"
            ]
          },
          "responseText": {
            "type": [
              "string",
              "null"
            ]
          },
          "surchargeAmount": {
            "type": [
              "string",
              "null"
            ]
          },
          "cashDiscount": {
            "type": [
              "string",
              "null"
            ]
          },
          "cardholderName": {
            "type": [
              "string",
              "null"
            ]
          },
          "terminal": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Status": {
        "type": "string",
        "enum": [
          "approved",
          "declined",
          "no_connection",
          "error",
          "pending",
          "cancelled"
        ]
      },
      "DeclineCategory": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "retain_card",
          "suspected_fraud",
          "invalid_cvv",
          "invalid_card",
          "insufficient_funds",
          "expired_card",
          null
        ],
        "description": "Null unless the processor code is one worth acting on; error always has the full reason."
      },
      "SaleResponse": {
        "type": "object",
        "properties": {
          "storeyTransactionId": {
            "type": "string",
            "description": "The Valet Ticket."
          },
          "referenceId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/Status"
          },
          "authCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "rrn": {
            "type": [
              "string",
              "null"
            ]
          },
          "card": {
            "$ref": "#/components/schemas/Card"
          },
          "amounts": {
            "$ref": "#/components/schemas/Amounts"
          },
          "refundedAmount": {
            "type": "number"
          },
          "voidedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "receipt": {
            "$ref": "#/components/schemas/Receipt"
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Processor's real decline/error reason; null when approved."
          },
          "declineCategory": {
            "$ref": "#/components/schemas/DeclineCategory"
          },
          "channel": {
            "type": "string",
            "enum": [
              "terminal",
              "key_entry"
            ]
          },
          "capturedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "replayed": {
            "type": "boolean",
            "description": "True when referenceId was seen before."
          },
          "paymentMethod": {
            "description": "Only when saveCard was sent: the saved card, or null."
          },
          "saveCardError": {
            "type": [
              "string",
              "null"
            ],
            "description": "Only when saveCard was sent."
          }
        }
      },
      "RefundRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Omit to refund everything remaining."
          }
        }
      },
      "ReversalResponse": {
        "type": "object",
        "properties": {
          "storeyTransactionId": {
            "type": "string"
          },
          "referenceId": {
            "type": "string"
          },
          "reversalId": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "refund",
              "void"
            ]
          },
          "amount": {
            "type": "number"
          },
          "status": {
            "$ref": "#/components/schemas/Status"
          },
          "authCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "rrn": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "declineCategory": {
            "$ref": "#/components/schemas/DeclineCategory"
          },
          "channel": {
            "type": "string",
            "enum": [
              "terminal",
              "key_entry"
            ]
          },
          "refundedTotal": {
            "type": "number"
          },
          "remaining": {
            "type": "number"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Plain-language reason."
          }
        }
      }
    }
  }
}