1 · Quote & accept
Phase 1 of the flow. Felix asks Tesser (via Slack) for GTQ; Tesser asks Nexa for a price, shows it to Felix, and locks it when Felix says yes. Both operations are [N] Nexa-hosted, and Tesser calls them.
Step 1 · Create the quote [N]
Operation: POST /v1/quotes: Create quote (op 1)
· hosted by Nexa, called by Tesser.
Tesser mints tesser_quote_id (UUIDv4) before the call. It is both the
identifier every later call and webhook uses, and the idempotency key. The
request carries the customer (FELIX at launch) and desired_gtq, the GTQ
Felix wants to end up with.
Nexa replies with the price and with usdc_amount, the USDC Felix must
deliver, computed as quoted_gtq / usdc_gtq_price, rounded up at 6dp
so that delivering exactly usdc_amount always covers quoted_gtq. Tesser
echoes that number and never derives it locally: with two calculators the
sides can disagree in the sixth decimal.
quoted_gtqmay be less thandesired_gtq. That is a partial fill from limited liquidity. Accepting acceptsquoted_gtq; the remainder is a new quote.expires_atis the acceptance deadline. Unaccepted quotes expire silently: there is no expiry webhook. Tesser knowsexpires_atand times out locally.- Retrying with the same
tesser_quote_idand an equivalent body returns201with the quote's current state. The same ID with a different body is409 QUOTE_CONFLICT.
How long a window Nexa can offer, and whether it is fixed or liquidity-dependent, is open question 1.
What can go wrong: create quote
error_code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | Malformed amount, unknown customer, bad UUID. |
UNAUTHENTICATED | 401 | Client certificate or customer API key missing/invalid. |
QUOTE_CONFLICT | 409 | Same tesser_quote_id, different body. |
NO_LIQUIDITY | 422 | Nexa cannot quote any amount right now. |
INTERNAL_ERROR | 500 | Unexpected failure on Nexa's side. |
Step 2 · Accept the quote [N]
Operation: POST /v1/quotes/{tesser_quote_id}/accept: Accept quote (op 2)
· hosted by Nexa, called by Tesser.
The body is empty ({}): the path identifies the quote, and acceptance is of
quoted_gtq by definition. Accepting locks the rate and starts the
deposit_deadline clock.
deposit_deadlineis how long Nexa holds the locked rate waiting for the first deposit report. After the deadline with nothing reported, the quote isEXPIREDand later calls get409 QUOTE_EXPIRED.- Re-accepting an already-
ACCEPTED(or later-state) quote returns200with the quote's current object. It is idempotent, not an error, so a retry after a network blip can never false-fail. - An unknown
tesser_quote_idis404 QUOTE_NOT_FOUND.
The deadline length, and whether an additional-funds request extends it, is open question 2.
What can go wrong: accept quote
error_code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | Malformed path parameter or body. |
UNAUTHENTICATED | 401 | Client certificate or customer API key missing/invalid. |
QUOTE_NOT_FOUND | 404 | No quote with this tesser_quote_id. |
QUOTE_EXPIRED | 409 | expires_at passed before acceptance. |
INTERNAL_ERROR | 500 | Unexpected failure on Nexa's side. |
Recovering state
If Tesser loses track of a quote, the optional
GET /v1/quotes/{tesser_quote_id}
[N] answers "what state do you think it's in?" in one call.
Next: 2 · Deposit reporting.