Search docs

Find a documentation page

Errors

RFC 7807 problem documents and the full error catalog for the 2QUIC Marketplace API.

Every error from the 2QUIC Marketplace API is an RFC 7807 problem document served with Content-Type: application/problem+json. Unlike successful responses, error bodies are flat: there is no {data, meta} envelope around them.

The problem document

A real response from POST /v1/leases when the requested epoch range overlaps an existing exclusive lease:

HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{
  "type": "https://docs.swqos.dev/errors/lease-slot-taken",
  "title": "Lease slot already booked",
  "status": 409,
  "detail": "Requested epoch range overlaps an existing lease for this validator.",
  "extensions": {
    "conflicting_lease_id": "01JXF6Y0Q8ZJ4T2M9R3W5B7DKE"
  }
}
FieldMeaning
typeStable URI ending in the error slug. Use the slug as your machine-readable discriminant. Do not dereference the URI, this catalog page is the reference.
titleShort human summary. Constant for a given error class.
statusHTTP status code, repeated in the body for non-HTTP consumers.
detailHuman explanation specific to this occurrence. May vary, do not parse it.
instanceOptional. The request path this problem refers to.
extensionsOptional open object carrying domain-specific fields, for example conflicting_lease_id on a booking conflict.

Branch on the slug at the end of type (for example lease-slot-taken), never on title or detail text. The slug is the stable contract.

Retry guidance at a glance

  • 400, 401, 403, 404, 422: your request is wrong or the resource state does not allow the action. Retrying the same request will fail the same way. Fix the request or the workflow first.
  • 409: a conflict with concurrent or existing state. Some are retryable (idempotency-conflict, extension-race, concurrent-rotation), most mean "pick something else".
  • 429: rate limited. Honor Retry-After and the X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers. Limits are listed on the authentication page.
  • 5xx: transient server or upstream failure. Retry with exponential backoff. Writes guarded by Idempotency-Key are safe to retry with the same key.

Error catalog

400 Bad Request

SlugWhen it firesClient behaviour
idempotency-key-missingIdempotency-Key header absent on one of the three guarded POSTs (/v1/leases, /v1/leases/{id}/extend, /v1/validators/{id}/leases/{lease_id}/revoke)Send a fresh ULID key and retry
idempotency-key-malformedThe key is not a 26-character Crockford base32 ULIDGenerate a proper ULID
invalid-cursorPagination cursor is not a cursor this API issuedRestart from the first page, never fabricate cursors
invalid-limitlimit outside the allowed range (validators 1 to 50, audit log 1 to 200)Clamp the value
invalid-epoch-startepoch_start exceeds the i32 range. Booking does not reject past epochs, so validate the window client-side before payingSend a real epoch number
invalid-epoch-countepoch_count outside 1 to 30Fix the count
invalid-epoch-rangeThe requested epoch window fails validationFix the window
invalid-price-boundprice_min_lamports / price_max_lamports filter invalid on GET /v1/validatorsFix the filter
lease-total-overflowThe lease total (price times epochs) overflows the supported amountReduce epoch_count
invalid-starsRating stars outside 1 to 5Fix the value
invalid-commentRating comment fails validationShorten or clean the comment
invalid-reply-textValidator rating reply fails validationShorten or clean the reply
register-timestamp-skewRegistration challenge timestamp outside the allowed skew window (5 minutes)Re-sign the challenge with a fresh timestamp
register-invalid-fieldA registration field fails basic validationFix the field named in detail
too-many-reportsUsage ingest batch above 1024 reportsSplit the batch
signs-delta-overflowA usage report signs_delta would overflow the stored counterFix the reporting agent, deltas of this size are a bug

401 Unauthorized

SlugWhen it firesClient behaviour
auth-requiredMissing or invalid bearer credentials on an authenticated routeAuthenticate, see authentication
user-not-provisionedThe session was verified but could not be resolved to a marketplace userSign in again through the web app
register-signature-invalidThe Ed25519 challenge signature on POST /v1/validators or PUT /v1/validators/payout-wallet does not verifyRe-sign with the validator identity key and check the exact challenge byte layout, see validator endpoints

403 Forbidden

SlugWhen it firesClient behaviour
forbiddenAuthenticated, but you do not own the resource or lack the required roleDo not retry, check which identity you are calling with
cannot-lease-own-validatorA trader tried to book a lease on a validator they registered themselvesBook a different validator
lease-not-activePOST /v1/leases/{id}/token on a lease that is not in the active stateIf the lease is pending_payment, complete the Solana Pay payment and wait for settlement. If revoked or completed, stop minting tokens
lease-epoch-out-of-rangeA token was requested outside the lease's paid epoch windowExtend the lease or stop, see trader endpoints

404 Not Found

SlugWhen it firesClient behaviour
lease-not-foundNo lease with that public_id visible to youCheck the ID
validator-not-foundNo validator with that public_idCheck the ID, the validator may have been taken down
credentials-already-shownSecond call to the one-shot POST /v1/leases/{id}/credentials revealThe cleartext token is shown exactly once. If you lost it, rotate via POST /v1/leases/{id}/api-keys
rating-not-foundReply attempted on a rating that does not existCheck the rating ID
onboarding-session-not-foundUnknown or expired onboarding tokenStart a new onboarding session from the web funnel
solana-pay-memo-not-foundPOST /v1/solana-pay/{memo} with an unknown, expired, or already-paid memo. The GET on the same path always returns 200 with the label and icon, so it cannot be used to probe memo validityThe pending-payment window is 15 minutes. If it elapsed, book again to get a fresh memo

409 Conflict

SlugWhen it firesClient behaviour
lease-slot-takenAn exclusive booking (or an extension) overlaps an existing pending_payment or active lease. extensions.conflicting_lease_id is included when the overlapping lease could be resolvedPick a different epoch window or validator. Do not blind-retry, the slot is held
shared-capacity-fullA shared booking found every seat taken for at least one epoch in the requested rangePick a different window, or book exclusive when the range frees up
idempotency-conflictA request with the same Idempotency-Key is still in flightWait per Retry-After (1 second) and retry with the same key
extension-already-pendingThe lease already has an unpaid extension memoPay the pending extension, or wait for the rollback sweep to expire it
extension-raceTwo extension attempts raced on the same leaseRetry once
already-ratedThe lease already has a ratingOne rating per lease, do not retry
already-repliedThe rating already has a validator replyOne reply per rating, do not retry
concurrent-rotationTwo API key rotations raced on the same leaseRetry once
register-nonce-replayThe registration challenge nonce was already usedGenerate a fresh nonce and re-sign
validator-already-registeredThe validator pubkey is already registeredUse the existing registration, or contact support if you lost the API key
lease-already-revokedRevoke called on a lease that is already revokedTreat as success, the lease is gone
payer-is-payout-walletPOST /v1/solana-pay/{memo} with account equal to the validator's payout walletPay from a different wallet, self-payment would corrupt settlement matching

422 Unprocessable Entity

SlugWhen it firesClient behaviour
idempotency-key-reusedSame Idempotency-Key re-sent with a different request bodyUse a new key for the new request. Replays must be byte-identical
shared-not-offeredA mode: "shared" booking against a validator with shared leasing disabledBook exclusive or pick another validator
lease-not-activeBooking, credentials, or refund flow hit a lease whose state does not allow the actionCheck GET /v1/leases/{id} for the actual state before acting
lease-expiredThe action targets a lease whose paid window has already elapsedBook a new lease
extension-exceeds-capThe extension would push the total span past 30 epochsRequest fewer epochs, the 30-epoch cap is absolute per lease
rating-not-yet-eligibleRating attempted before the lease completed or before the minimum usage threshold (10,000 TEE signatures)Wait until the lease completes with real usage
invalid-accountPOST /v1/solana-pay/{memo} with an account that is not a valid base58 Solana pubkeySend the payer's pubkey in base58
invalid-onboarding-tokenThe onboard_token passed to registration is invalidMint a fresh session from the web funnel
not-a-solana-validatorThe registration pubkey is not a known Solana vote-account identityCheck the pubkey, registration verifies it against the cluster
register-price-requiredRegistration without an exclusive priceSet price_sol
register-invalid-tierUnknown tier value (allowed: economy, standard, performance)Fix the tier
register-invalid-regionUnknown region valueFix the region
register-shared-config-incompleteShared offering enabled at registration with price or seat cap missingProvide both shared_price_sol and max_concurrent_shared_leases
register-shared-price-invalidShared price fails validation at registrationFix the price
register-shared-seats-invalidShared seat cap fails validation at registration (must be at least 1)Fix the seat cap
shared-config-incompletePATCH /v1/validators/{id} would leave the shared offering half-configuredSend price and seat cap together
patch-validator-emptyPATCH /v1/validators/{id} with no fields to changeSend at least one field

429 Too Many Requests

SlugWhen it firesClient behaviour
rate-limit-exceededA token bucket ran dry. Per-route limits: token minting 12/min per identity, lease creation 10/min per trader, validator browse 600/min per IP, everything else 60/minHonor Retry-After. Pace token refreshes around the roughly 10-minute JWT TTL instead of polling

5xx Server errors

SlugStatusWhen it firesClient behaviour
database-error500Unexpected database failureRetry with backoff
internal-error500Unexpected internal failureRetry with backoff
jwt-sign-failed500The KMS signing call failed on the token routeRetry, keep using your current JWT until its exp
audit-row-encoding-failed500The token route could not record its audit rowRetry
token-hash-failure500API token hashing failed during credentials reveal or rotationRetry
admin-user-not-provisioned500An admin route could not resolve the acting staff userStaff-only, contact the platform team
solana-rpc-unavailable503 or 502Upstream Solana RPC is down. 503 on registration, stake checks, and Solana Pay transaction building, 502 on leader-schedule routesRetry with backoff, the marketplace itself is healthy

Booking conflicts in detail

The two booking 409s are deliberately distinct:

  • lease-slot-taken is the exclusive-overlap conflict. There is one specific lease in your way, and when the API can resolve it you get its ID in extensions.conflicting_lease_id. A pending_payment lease holds the slot too, so a conflict can clear by itself if that booking goes unpaid past the 15-minute payment window.
  • shared-capacity-full is the seat-pool conflict for mode: "shared". There is no single conflicting lease, the seat count is exhausted for at least one epoch in your range.

A practical booking loop: on lease-slot-taken or shared-capacity-full, re-fetch the validator's availability from GET /v1/validators/{id} and offer the trader the next free window. On shared-not-offered (422), fall back to an exclusive quote.

For extensions, extension-already-pending means your previous extension attempt produced a payment memo that has not settled yet. Pay it or wait for the sweep to roll it back, do not stack new attempts. extension-race is a pure concurrency artifact and is safe to retry once.

Idempotency error semantics

The three guarded POSTs (/v1/leases, /v1/leases/{id}/extend, /v1/validators/{id}/leases/{lease_id}/revoke) interact with errors in a specific way:

  • Only 200/201 responses are cached and replayed (for 24 hours, byte-identical body required).
  • An error response releases the key, so retrying with the same key re-runs the handler. This is the correct recovery path after a 5xx.
  • 409 idempotency-conflict means a duplicate is in flight right now, retry after 1 second with the same key.
  • 422 idempotency-key-reused means you changed the body under a used key, mint a new key.

Full mechanics on the authentication and rate limits page.