Valet Key
The API key issued per location. Every request identifies itself with one — it says which of your businesses and which counter is charging the card.
Developer docs
Two credentials, three endpoints, and a widget or a plain API. No payments background required.
The vocabulary
The API key issued per location. Every request identifies itself with one — it says which of your businesses and which counter is charging the card.
The device ID issued per station. A tag is either a physical terminal — the customer taps where they already are — or a key-entry station, where the card is typed into Storey's hosted payment window. The sale endpoint serves terminals; key-entry stations go through a session.
The transaction ID returned after every sale (storeyTransactionId). Reference it to refund or void later. No ticket, no refund.
Authentication
Every call carries the location's Valet Key and the station's Valet Tag. Together they resolve which business, which counter and which terminal the request belongs to — there is nothing else to sign, token or renew. Keep both values server-side; requests are rate limited and every call is logged.
Headers
X-Valet-Key: vk_live_XXXXXXXXXXXXXXXXXXXXXXXX
X-Valet-Tag: vt_XXXXXXXXXXXXXXXXXXXXEndpoints
The base URL is https://storeypos.com. All three are POSTs and all three answer with the same field names, so one response handler covers the lot.
Send your own referenceId — it is your idempotency key, and repeating it returns the original answer instead of charging twice. subtotal is required; taxAmount, tipAmount and shippingAmount default to 0; and total must equal their sum — the server checks the maths and rejects the call otherwise. A replayed flag of true means this referenceId was seen before and the original result was returned.
Status is approved, declined, no_connection or error.
This endpoint is for terminal stations only. If the Valet Tag is a key-entry station, the call is rejected and you are pointed at POST /api/public/valet/session instead — a card number can never be sent to Storey over this API.
Request — cURL
curl -X POST https://storeypos.com/api/public/valet/sale \
-H "X-Valet-Key: vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "X-Valet-Tag: vt_XXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{"referenceId":"order-10482","subtotal":40.00,"taxAmount":2.50,"total":42.50}'Response
{
"storeyTransactionId": "vlt_9f2c8e1a4b3d",
"referenceId": "order-10482",
"status": "approved",
"authCode": "0F3C21",
"rrn": "042619005831",
"card": { "brand": "visa", "last4": "4242", "entryMode": "contactless" },
"receipt": {
"batchNo": "12",
"tranNo": "VC07",
"responseText": "SUCCESS",
"surchargeAmount": "0.00",
"cashDiscount": "0.00",
"cardholderName": "JANE SAMPLE",
"terminal": "2319993484"
},
"amounts": {
"subtotal": 40.00,
"taxAmount": 2.50,
"tipAmount": 0,
"shippingAmount": 0,
"total": 42.50
},
"refundedAmount": 0,
"voidedAt": null,
"error": null,
"capturedAt": "2026-09-16T18:22:04Z",
"createdAt": "2026-09-16T18:21:58Z",
"replayed": false
}Sale and reversal responses also carry declineCategory. It is null unless the processor's own code is one of the few worth acting on; the plain error text is always the full reason.
Include amount for a partial refund, or omit the body to return everything left. The refund runs on the very terminal that took the payment. The response carries refundedTotal and remaining so you always know where the sale stands; a refund beyond what remains is rejected before anything is sent to the terminal. Refunds can be switched off per location — the error tells you plainly if they are.
Request — cURL
curl -X POST https://storeypos.com/api/public/valet/sale/vlt_9f2c8e1a4b3d/refund \
-H "X-Valet-Key: vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "X-Valet-Tag: vt_XXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{"amount": 42.50}'Response
{
"storeyTransactionId": "vlt_9f2c8e1a4b3d",
"referenceId": "order-10482",
"reversalId": "rvs_5d71b09c2e44",
"kind": "refund",
"amount": 42.50,
"status": "approved",
"authCode": "1A7D04",
"rrn": "042619005831",
"error": null,
"refundedTotal": 42.50,
"remaining": 0,
"createdAt": "2026-09-17T09:14:31Z"
}No body. A void cancels the sale the same day, before settlement; after that the money has moved and a refund is the only route — the response says so if you try. A sale that has already been partially refunded can only be refunded, never voided.
Request — cURL
curl -X POST https://storeypos.com/api/public/valet/sale/vlt_9f2c8e1a4b3d/void \
-H "X-Valet-Key: vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "X-Valet-Tag: vt_XXXXXXXXXXXXXXXXXXXX"Response
{
"storeyTransactionId": "vlt_9f2c8e1a4b3d",
"referenceId": "order-10482",
"reversalId": "rvs_8c20e5f71a93",
"kind": "void",
"amount": 42.50,
"status": "approved",
"authCode": null,
"rrn": "042619005831",
"error": null,
"refundedTotal": 0,
"remaining": 0,
"createdAt": "2026-09-16T18:26:12Z"
}Every response carries the real reason for a decline — never a generic message — so your software can show the customer something useful.
The widget
The widget is a self-contained, shadow-DOM component — it always looks like Storey, never like your site, and needs no styling from you. Open it with a session token before charging the card; it walks the customer through confirm, tap and receipt, and reports the outcome back to your code.
The session tells the widget which kind of station it is working with. For a terminal it waits at the card machine; for a key-entry station it opens Storey's hosted payment window, where the card is typed into the payment provider's own secure field. Either way your code sees the same result.
HTML
<script src="https://storeypos.com/valet-widget.js"></script>
<script>
StoreyValet.open({
token: session.token,
onComplete: (result) => { /* result.status: "approved" | "declined" */ },
onCancel: () => {},
});
</script>Valet, by Storey
Tell us about your software and we'll set you up with your first Valet Key and Valet Tag.