Transactions
A transaction records an order that an agent and a merchant agreed on. It is opened through a checkout session, read back by id, and closed by a confirmation or a cancellation.
The purchase path is the checkout session: open one, complete it, and the buyer pays the merchant on the merchant's own checkout. Cresva never takes or holds the money, so a transaction record here is a record of what was agreed rather than a receipt for something we took.
Withdrawn 2026-09-10
POST /transactions returns 410 Gone
This endpoint used to create a transaction directly, and it priced the order from the request body rather than from the catalogue. The refusal says so itself, in its own words, and the body below is the source for this paragraph rather than a run recorded anywhere a reader could check.
It answers 410 rather than 401 or 400 because the resource is withdrawn rather than refused. No key and no better formed body will make it work, and an agent that reads a 410 stops retrying. The response names the reason and points at what still works, so an agent can re-plan instead of guessing.
{
"error": {
"code": "gone",
"message": "Creating a transaction through this storefront is withdrawn. It priced orders from the request body rather than from the catalogue, so it could not be trusted to record what an order actually cost.",
"reason": "server_side_pricing_absent",
"withdrawn_on": "2026-09-10",
"still_available": {
"read_the_catalogue": "GET /api/storefront/{brandId}/products",
"agree_a_price": "POST /api/storefront/{brandId}/negotiate",
"read_a_past_transaction": "GET /api/storefront/{brandId}/transactions/{id}"
},
"to_buy": "Ordering is not available from this storefront. Take the product to the merchant's own checkout."
}
}/checkout/sessionsOpen a checkout session. This is what replaced direct transaction creation.
Authentication: secret key
The request names products and quantities. Every price is read from the catalogue, and any price in the body is ignored. There is no field here a price can arrive in, which is the whole difference between this route and the one it replaced.
itemsarrayObjects of { productId, quantity }. Send this, or one of the three alternatives below.negotiationIdstringOpen the session from a negotiation that already agreed a price.bundleIdstringOpen the session from a bundle.offerClaimIdstringOpen the session from a claimed offer.curl -X POST "https://cresva.ai/api/storefront/$BRAND_ID/checkout/sessions" \
-H "Authorization: Bearer $CRESVA_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"productId":"prod_abc","quantity":1}]}'A session that can never complete is created as refused rather than open, so an agent is told now instead of polling something that will never move. Sending no items returns 400 with a message naming the four ways to open one.
/checkout/sessions/:sessionId/statusRead a session's current state.
Authentication: secret key
curl "https://cresva.ai/api/storefront/$BRAND_ID/checkout/sessions/$SESSION_ID/status" \
-H "Authorization: Bearer $CRESVA_SECRET_KEY"/checkout/sessions/:sessionId/completeComplete the session. Creates the merchant's order with payment marked pending.
Authentication: secret key
This creates a draft order on the merchant's own Shopify store and returns a link the buyer uses to pay the merchant. The purchase path is real and was proven paid end to end on 2026-09-14, on order ORD-CRESV-D19DEE, 9.95 USD. Nothing was created in production on the 15th, which is the date this page used to give.
{
"order": {
"shopifyOrderId": null,
"draftOrderId": "gid://shopify/DraftOrder/...",
"name": "#...",
"financialStatus": "pending",
"alreadyPlaced": false
},
"payment": {
"taken": false,
"method": "merchant_checkout",
"invoice_url": "https://<shop>.myshopify.com/...",
"note": "Cresva does not take payment. Open this URL to pay the merchant on their own checkout. The order is created when the payment succeeds, not before.",
"status_url": "/api/storefront/{brandId}/checkout/sessions/{id}/status"
}
}payment.taken is permanently false, and it does not mean nobody paid. It answers whether Cresva took the money, and the answer will never be yes. Whether the buyer has paid is a different question with a different field: poll status_url. At the instant this response is returned nobody has had the chance to pay yet.
financialStatus is read back from Shopify rather than asserted by us. It is pending here and should be: reporting paid would be a lie a merchant would discover when they tried to fulfil.
Hand the invoice URL to the shopper. Never fetch it server side. Opening it on the shopper's behalf is the step that turns an assistant into a purchaser. Cresva never follows it either.
A null invoice_url is a real answer, not a missing field: no payable link was issued, so nothing has been ordered and there is nothing to retry. The note says so in words for exactly that reason.
curl -X POST "https://cresva.ai/api/storefront/$BRAND_ID/checkout/sessions/$SESSION_ID/complete" \
-H "Authorization: Bearer $CRESVA_SECRET_KEY"/transactions/:transactionIdRead one transaction by id.
Authentication: none required
curl "https://cresva.ai/api/storefront/$BRAND_ID/transactions/$TRANSACTION_ID"There is no endpoint that lists a brand's transactions. Reading one requires its id.
/transactions/:transactionId/confirmConfirm a transaction.
Authentication: secret key
curl -X POST "https://cresva.ai/api/storefront/$BRAND_ID/transactions/$TRANSACTION_ID/confirm" \
-H "Authorization: Bearer $CRESVA_SECRET_KEY"/transactions/:transactionId/cancelCancel a transaction.
Authentication: secret key
curl -X POST "https://cresva.ai/api/storefront/$BRAND_ID/transactions/$TRANSACTION_ID/cancel" \
-H "Authorization: Bearer $CRESVA_SECRET_KEY"The confirmation window
OSP v3.0 replaced escrow with a confirmation window, and the difference is the point: the window is a status, not a custody arrangement. It records whether a buyer confirmed or disputed. It does not hold money, because there is no money here to hold.
POST /transactions/:id/confirmation-window/confirmendpointCloses the window early. The buyer is satisfied.POST /transactions/:id/confirmation-window/disputeendpointRecords a dispute. Cresva does not adjudicate it and does not distribute anything, because nothing is in Cresva's custody. Resolution is between the buyer and the merchant.States
A transaction moves created_txn to confirmed_txn to paid_txn to fulfilling_txn to completed_txn, or into cancelled_txn, refunded_txn or disputed_txn.
There is no ESCROWED state. It was removed in v3.0 because it described Cresva as holding funds, which Cresva has never done and holds no licence to do.