Guarantees & limitations
What v1 promises, and the edges to design around.
What v1 guarantees
- Exactly-once charge per idempotency key. Re-POST the same key safely; the server dedupes on
(tenantId, idempotencyKey). - End-to-end HMAC signing. Every inbound request and every outbound webhook is signed; inbound and webhook secrets are separate; ±5-minute replay window.
- At-least-once webhook delivery. Retried on a published backoff schedule (up to 8 attempts / ~8 hours), dedupable on
X-GFN-Delivery. - Held cap is the ceiling.
quote.maxis the most you can be charged for a submission; the unused remainder is refunded at settlement. - Frozen-additive v1. New optional fields and new endpoints/events may appear without a version bump; existing field shapes and semantics do not change under
/v1/. Breaking changes go to/v2/with ≥90 days of parallel/v1/availability. - No cross-tenant leakage. A submission or intent owned by another tenant returns
404, never confirming existence. - Fiat, single-issuer credits. Consumptive, non-transferable, non-redeemable; no token, wallet, or stored value (ADR-046).
What v1 does NOT guarantee
- Synchronous results.
POST /v1/submissionsreturns a quote + hold, not an outcome. The result arrives later via webhook (or poll). Do not block a user request on settlement. - Webhook ordering. Treat events as independent and idempotent; reconcile against
GET /v1/submissions/:idif you need authoritative current state. Dedupe before applying side effects. - A stable
estimated.quote.estimatedis informational; the binding number isquote.max(held) and the eventualactualCredits(settled). - Delivery to an unconfigured webhook. If you have no
webhook_urlset, events are silently dropped — poll instead, or configure a URL. - Capacity. If no operator can accept a submission at dispatch, you get
503 dispatch_unavailableand the hold is released. Retry later; there is no local edge queue holding your work. - Raw browser / CDP / Playwright / proxy access. v1 is a typed-task API. You submit declared job types; you never receive raw browser, proxy, or desktop-control access to nodes (by design — see Build on L1).
- Crypto rails as GA. Fiat is the GA customer rail. The inbound Solana Pay USDC rail is a preserved continuity exception (ADR-047), off unless explicitly enabled — do not depend on it being present.
- The
402balancefield. Deferred post-beta; therequiredfield is what you get at the insufficient-credits denial.
Field-name reconciliation (read once)
| Surface | Id field |
|---|---|
POST /v1/submissions | submissionId |
Webhook envelope data | submissionId |
GET /v1/submissions/:id (raw wire) | id (SDK normalizes to submissionId) |
Webhook data is event-specific and is NOT the GET response shape. Always design consumers to ignore unrecognized keys (frozen-additive).
Reference:
docs/V1-API.md §8 (versioning) and §2.2 (id field-name note), in the repository.