Skip to main content

Errors

Every error response is JSON shaped like:

{ "error": "machine_code", "detail": "human-readable info" }

Standard codes

HTTPerrorWhat's wrongFix
400invalid_bodyRequest body failed schema validationRead detail for the offending path
401unauthorizedMissing or malformed Authorization headerSend Bearer gvk_live_…
401invalid_keyAPI key not found / deactivatedIssue a new key in the portal
401invalid_session_tokenSession JWT failed verify or expiredMint a fresh one
402credit_limit_reachedYour store credit pool is emptyBuy a credit pack
402end_customer_quotaEnd-customer hit their plan quotaShow paywall; PATCH resetPeriod on next renewal
403account_inactiveStore account suspendedContact support
403customer_inactiveThe customer is PAUSED or CANCELLEDPATCH them back to ACTIVE
403domain_not_allowedOrigin not on the API key's allowlistAdd the host in the portal
403token_store_mismatchJWT was minted by a different storeUse the JWT minted with your API key
403plan_not_foundPlan id doesn't belong to the callerVerify the id
404not_foundCustomer / plan id unknown to the callerVerify the id

end_customer_quota reasons

/api/tryon/track includes a reason when it returns 402 with error=end_customer_quota:

reasonMeaning
quota_exhaustedPlan limit reached this period
no_planCustomer has no plan attached, or plan is inactive
pausedCustomer status is PAUSED
cancelledCustomer status is CANCELLED

Idempotency

Most write endpoints are naturally idempotent:

  • POST /customers upserts on (storeUserId, externalId)
  • Plan / customer PATCH is by definition idempotent

POST /plans is not idempotent — sending it twice creates two plans. Track plan ids on your side.

Rate limits

There is currently no public rate limit. We will publish exact numbers before we enforce them; for now keep concurrency reasonable (≤ 50 RPS per store).