Documentation Index
Fetch the complete documentation index at: https://docs.vaultgraph.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Status: v1.0 (stable). Backwards-incompatible changes will bump the major version.
This document is the canonical reference for merchants implementing a VaultGraph commerce backend over HTTPS. It defines the request/response envelope, signing scheme, error model, idempotency contract, retry/timeout behavior, and pagination convention.
The @vaultgraph/sdk/adapter helper wraps everything below in a single typed factory.
1. Transport
- Direction: VaultGraph → merchant. The merchant exposes one HTTPS endpoint; the gateway calls it.
- Method:
POST.
- URL shape:
{endpoint_url}/{method} — the configured endpoint_url joined with the method name as a single path segment. The path prefix is the merchant’s choice — VaultGraph does not reserve or require any particular name. Example: https://shop.example.com/webhooks/vaultgraph/createCheckout.
- TLS: required. The gateway will not call plain
http:// endpoints.
- Content-Type:
application/json; charset=utf-8 on both directions.
2. Request envelope
{
"protocol": "1.0",
"method": "createCheckout",
"params": { "input": { "currency": "GBP" } }
}
| Field | Type | Notes |
|---|
protocol | string | Wire protocol version. Currently "1.0". Merchants MUST reject requests with an unsupported protocol. |
method | string | One of the methods in §6. Mirrors the path segment. |
params | object | Method-specific arguments. Always an object (never an array or primitive). |
| Header | Required | Notes |
|---|
Content-Type | yes | application/json. |
X-VaultGraph-Version | yes | Same value as protocol in the body. Lets edge proxies short-circuit rejection. |
X-VaultGraph-Signature | yes | See §3. |
X-VaultGraph-Idempotency-Key | on mutating methods (§5) | UUID v4 generated per logical operation by the gateway. |
3. Signing
Authentication is HMAC-SHA256 over ${timestamp}.${idempotencyKey}.${rawBody}.
- The gateway takes the current Unix timestamp in seconds.
- It computes
signature = HMAC_SHA256(secret, "${timestamp}.${idempotencyKey}.${rawBody}") and hex-encodes it. ${idempotencyKey} is the value of the X-VaultGraph-Idempotency-Key header (§5); on non-mutating methods, which carry no idempotency key, it is the empty string.
- It sets
X-VaultGraph-Signature: t=<timestamp>,v1=<hex_signature>.
Merchants MUST:
- Reject requests where
X-VaultGraph-Signature is missing or malformed.
- Reject requests where
|now - timestamp| > 5 minutes. This is the replay window.
- Recompute the HMAC over the timestamp, the
X-VaultGraph-Idempotency-Key header value (empty string when absent), and the raw body bytes (not a re-serialized JSON), and reject on mismatch. Use a constant-time comparison.
The t=…,v1=… shape leaves room for v2 to ship a new digest in the future without breaking existing verifiers.
Secret rotation
VaultGraph issues the signing secret when you create the commerce backend in the portal and lets you rotate it from the backend’s settings page. After a rotation the gateway begins signing with the new secret within 60 seconds. The protocol does not multiplex signatures, so coordinating a dual-accept window on your verifier (accept both old and new during the cutover) is the merchant’s responsibility.
4. Response envelope
Success:
{
"data": {
/* method-specific result */
}
}
Failure:
{
"error": {
"code": "checkout_not_open",
"status": 409,
"detail": "Checkout ck_abc has already completed."
}
}
| Field | Type | Notes |
|---|
data | any | Required on success. Method-specific shape (§6). |
error.code | string | Required on failure. See §7 for the recognized set. |
error.status | integer | Optional. HTTP-style status the gateway should surface. Defaults to the HTTP response status. |
error.detail | string | Optional. Human-readable message safe for upstream display; do not include secrets or PII. |
The HTTP status code SHOULD mirror error.status. Returning a 2xx with an error field is tolerated (the gateway prefers the envelope) but discouraged.
A response with neither data nor error — or an HTTP error with no JSON body — is treated as commerce_backend_unavailable (HTTP 502).
5. Idempotency
The gateway sends X-VaultGraph-Idempotency-Key: <uuid> on every mutating method:
createCheckout
addLineItems
updateLineItem
removeLineItem
applyDiscount
setFulfillment
setBuyer
completeCheckout
Reads (searchProducts, getProduct, getCheckout, listFulfillmentMethods, getOrder, listOrders) do not carry an idempotency key.
The contract:
- The merchant SHOULD store the full response (status + body) keyed by the idempotency key.
- If the same key arrives a second time within the merchant’s TTL, the merchant SHOULD return the stored response verbatim instead of re-running the operation.
- Cache both success AND error responses. A mutating call that fails after committing a side effect (e.g. order written, downstream notification failed) must NOT re-execute on retry. The gateway treats merchant errors with recognized codes as deterministic answers and will not retry them, but a network blip mid-response can still cause a retry — the cache is what protects against the duplicate.
- Recommended TTL: 24 hours. Retries beyond that window are vanishingly rare; storage costs grow if you keep them forever.
The SDK helper exposes an idempotencyStore option that handles this for you given a { get(key), set(key, { status, body }) } interface — most merchants will plug in their existing cache layer (Redis, Postgres unlogged table, KV).
6. Methods
| Method | Mutating | params | data |
|---|
searchProducts | no | { input: SearchCatalogInput } (see Catalog search) | ProductPage ({ products: Product[], pagination?: { … } }) |
getProduct | no | { id: string } | Product or null → product_not_found (404) |
createCheckout | yes | { input: CheckoutCreateInput } | Checkout |
getCheckout | no | { id: string } | Checkout or null → checkout_not_found |
addLineItems | yes | { checkout_id: string, line_items: LineItem[] } | Checkout |
updateLineItem | yes | { checkout_id: string, line_item_id: string, quantity: integer ≥ 0 } | Checkout |
removeLineItem | yes | { checkout_id: string, line_item_id: string } | Checkout |
applyDiscount | yes | { checkout_id: string, code: string } | Checkout |
listFulfillmentMethods | no | { checkout_id: string } | FulfillmentMethod[] (see Fulfillment) |
setFulfillment | yes | { checkout_id: string, fulfillment_method_id: string, shipping_address?: ShippingDestination } | Checkout |
setBuyer | yes | { checkout_id: string, buyer: Buyer, billing_address?: BillingAddress } | Checkout |
completeCheckout | yes | { checkout_id: string } | Order |
getOrder | no | { id: string } | Order or null → order_not_found (404) |
listOrders | no | {} | Order[] |
TypeScript types for every params and data shape ship from @vaultgraph/sdk/adapter — install it for types even if you don’t use the handler factory in §10.
Catalog search
searchProducts takes { input: SearchCatalogInput }, where every field of SearchCatalogInput is optional — an empty input returns the first page of the whole catalog:
{
"query": "running shoes", // optional free-text search
"filters": {
// optional
"categories": ["footwear"], // OR logic across the list
"price": { "min": 5000, "max": 20000 }, // inclusive, integer minor units
},
"pagination": { "cursor": "…", "limit": 20 }, // optional; limit is clamped to ≤ 100
}
The merchant SHOULD honor query and filters; a merchant that cannot run a given filter MAY ignore it, but SHOULD NOT error.
searchProducts returns a ProductPage — { "products": Product[], "pagination"?: { … } }, not a bare array. Pagination is cursor-based and caller-driven:
- The merchant chooses the cursor encoding (offset, keyset, base64 JSON — whatever fits the underlying store). The gateway treats it as an opaque blob: it never inspects or generates the value.
- When more pages remain, the merchant returns
pagination: { "has_next_page": true, "cursor": "<token>" }. cursor MUST be present whenever has_next_page is true.
- On the last page the merchant either omits
pagination entirely or returns pagination: { "has_next_page": false }. total_count MAY be included on any page.
- The caller passes the returned
cursor straight back as input.pagination.cursor on the next call; the gateway forwards it unchanged.
- The gateway does not auto-paginate — each call returns exactly one page and the caller drives the loop.
listOrders has no pagination in v1.0. Merchants should cap returned rows at a reasonable limit (≤ 100) and document the limit out-of-band.
Fulfillment
listFulfillmentMethods returns the methods selectable for a checkout — each a FulfillmentMethod ({ id, name, description, amount, currency, method_type }), where amount is integer minor units and method_type is shipping, pickup, or digital.
setFulfillment accepts an optional shipping_address (a ShippingDestination — a postal address). When the chosen method’s method_type is shipping, the merchant MUST require a deliverable destination and SHOULD reject a missing or incomplete address with fulfillment_address_required (400). pickup and digital methods carry no address.
7. Error codes
The recognized set the merchant may emit:
| Code | Default status | When |
|---|
product_not_found | 404 | getProduct finds no row. |
variant_not_found | 404 | A variant id on a line-item is unknown. |
checkout_not_found | 404 | getCheckout / addLineItems / … on a missing checkout. |
checkout_not_open | 409 | Mutation on a completed or otherwise closed checkout. |
checkout_not_ready | 409 | completeCheckout before buyer / fulfillment are set. |
checkout_empty | 409 | completeCheckout on a checkout with no line items. |
line_item_not_found | 404 | updateLineItem / removeLineItem on an unknown line. |
discount_not_found | 404 | applyDiscount with an invalid code. |
fulfillment_method_not_found | 404 | setFulfillment with an invalid method id. |
fulfillment_address_required | 400 | setFulfillment for a shipping method with no address. |
order_not_found | 404 | getOrder finds no row. |
Any other code is mapped to commerce_backend_unavailable (HTTP 502) on the gateway side. The detail is preserved but the original code is replaced — agents and downstream consumers do not see unknown codes.
8. Timeout & retry
The gateway defaults:
- Per-attempt timeout: 10 seconds (configurable per backend in the portal).
- Retries: up to 2 (configurable per backend in the portal), with exponential backoff starting at 100 ms.
Retries fire on:
- network errors (DNS, TCP, TLS, socket close mid-response),
- response timeouts (the request aborted before headers),
- HTTP 5xx without a recognized error envelope.
Retries do not fire on a well-formed error envelope with a recognized code — those are deterministic answers. They also do not fire on 4xx without an envelope (treated as a 502 once, then surfaced).
Because every mutating method carries an X-VaultGraph-Idempotency-Key, the merchant can safely dedupe. Reads are naturally idempotent.
9. Versioning
- Envelope version is the
protocol field plus the X-VaultGraph-Version header. Additive payload changes (new methods, new optional fields on data) ship under the same envelope.
- Backwards-incompatible envelope changes will bump the major version (
1.0 → 2.0) and use a new header value. The gateway will continue to send 1.0 to backends that have not opted in.
- Merchants that don’t implement a method may return
{"error":{"code":"not_implemented","status":501}} — the gateway surfaces it as commerce_backend_unavailable. The @vaultgraph/sdk/adapter helper emits this envelope automatically for any method you don’t supply, so you can ship a partial implementation and grow into the surface incrementally.
10. Reference implementation
The @vaultgraph/sdk/adapter helper wraps signing, replay rejection, dispatch, idempotency caching, and error serialization in a single typed factory:
import { createAdapterHandler } from "@vaultgraph/sdk/adapter";
const handler = createAdapterHandler({
signingSecret: process.env.VAULTGRAPH_SIGNING_SECRET!,
idempotencyStore: kvBackedStore,
async searchProducts({ query, filters, pagination }) {
/* ... */
},
async getProduct(id) {
/* ... */
},
async createCheckout(input) {
/* ... */
},
// ...one method per row in §6 — every method is optional, anything you
// skip auto-responds with `not_implemented` (501).
});
// Plug into your HTTP framework — handler is just (req) => res.
See @vaultgraph/sdk on npm for the full surface.