Errors
Every error body has the same shape.
{
"error": {
"code": "invalid_limit",
"message": "limit must be between 1 and 200",
"retryable": false
}
}
codeis for your program. It is part of the contract and does not change.messageis for a reader. It is written to be relayed to an owner.retryablesays whether the same request can succeed later.
Read retryable rather than guess from the status, and rather than guess
from the code. One code can be retryable on one call and not on another,
because the two calls have two remedies.
Three fields that come and go
next_actionis the remedy, in plain language, where this surface knows one. Relay it to the owner. It is written for that.request_idnames the one request. Onlyinternalcarries it: a fault on our side tells you nothing you can act on, so it gives you the one thing an operator can look up.detailcarries a failed build's log. That log is the compiler telling whoever wrote the source what is wrong with it.detailis for build logs and nothing else.
An absent next_action is a message too. It says the message names
everything there is, and that this surface knows no call that mends it.
Do not invent one.
A resource that is not finished the moment you ask for it carries
status and next_action of its own, on a 200. Same field, same job.
Four codes you meet everywhere
| Code | Status | Retryable | When |
|---|---|---|---|
internal |
500 | yes | A fault on our side. Send the request again. If it keeps failing, POST /v1/feedback with the request_id. |
provider_error |
502 | it depends | An outside service failed. retryable says whether it had a temporary problem or refused. The message names which part of the platform, never the provider behind it. |
plan_limit |
402 | no | A cap the account's plan sets, or the size of what you sent. Time does not clear it. |
rate_limited |
429 | yes | A cap on how often. Wait, then send the same request again. Do not send a different one. |
platform_full |
503 | no | Clave has no room for a new site, draft or domain. The account is in line, and Clave emails the owner when there is room. Tell the owner. Do not retry. An account that started without an email is not in line: the message says what to do. |
There are three cap codes and no more. A cap that money or a smaller
request lifts is plan_limit. A cap that a wait lifts is rate_limited.
A cap for the whole platform is platform_full. Every cap on this surface
is one of the three.
Every code
retryable is no unless this table says otherwise.
The request
| Code | Status | Retryable | When |
|---|---|---|---|
unauthorized |
401 | no | No token, or a token this platform does not know. |
access_denied |
403 | no | The owner refused the access request. Do not open another one unless they ask. |
owner_only |
403 | no | A collaborator asked for something only the site's owner may do. |
verify_email_required |
403 | no | An account that started without an email can no longer make changes. Its time or its builds are used. Verify an email with POST /v1/account/verify, then send the request again. |
domain_not_the_owners |
403 | no | The domain and the site belong to different accounts. |
not_found |
404 | no | No such thing, or none you may see. |
invalid_json |
400 | no | The body is not valid JSON for this endpoint. |
body_too_large |
400 | no | The body is over 1 MiB. |
invalid_cursor |
400 | no | Use next_cursor from an earlier answer. |
invalid_limit |
400 | no | limit is outside the range this call takes. |
invalid_since |
400 | no | since is not an RFC3339 timestamp. |
invalid_period |
400 | no | period is not one this call reports on. |
invalid_query |
400 | no | query is missing or empty. |
invalid_format |
400 | no | format is not one this call writes. |
invalid_scope |
400 | no | scope is not one this call publishes. |
invalid_status |
400 | no | status is not one this call filters on. |
nothing_to_update |
400 | no | The body asks for no change. |
invalid_origin |
400 | no | The call came from a page this surface does not serve. |
Sign-in and tokens
| Code | Status | Retryable | When |
|---|---|---|---|
auth_disabled |
503 | no | Sign-in by email is off on this deployment. |
try_disabled |
503 | no | Trying Clave without an account is off on this deployment. |
signin_unavailable |
410 | no | The owner approved, and no account exists for the email. This deployment does not let an agent make one. |
invalid_email |
400 | no | The email is not a plain valid address. |
expired |
410 | no | The access request ran out before the owner answered. |
already_redeemed |
410 | no | That sign-in is finished. Start a new one. |
already_verified |
409 | no | The account has a verified email already. Nothing to do. |
verify_disabled |
503 | no | This deployment cannot send a verification link. |
not_a_grant |
409 | no | The subscription was paid for, not granted. |
current_token |
409 | no | A token cannot revoke itself. Mint another one first. |
invalid_expiry |
400 | no | expires_at is not in the future. |
invalid_name |
400 | no | A name is missing or empty. |
invalid_note |
400 | no | A suspension, or a pause or resume of a site's mail, has no note that says why. |
unknown_limit |
400 | no | Staff only: an account's limit override names a limit that cannot be set. |
invalid_value |
400 | no | Staff only: the value is not legal for that limit. |
mail_disabled |
503 | no | Staff only: this deploy does not send a site's mail. |
mail_not_paused |
409 | no | Staff only: the site's mail is not paused, so there is nothing to resume. |
email_change_in_app |
403 | no | A token cannot change the email. The owner changes it in the app. |
Sites and collaborators
| Code | Status | Retryable | When |
|---|---|---|---|
invalid_title |
400 | no | The title is missing or too long. |
invalid_template |
400 | no | No template of that name. |
site_archived |
409 | no | The site is archived, so it serves nothing. |
no_repositories |
503 | no | This deployment holds no repository root. |
invitations_unavailable |
503 | no | This deployment cannot send email, so it cannot invite. |
invalid_invitation |
400 | no | The invitation link is not one this platform wrote. |
invitation_accepted |
409 | no | That invitation is already taken. |
email_taken |
409 | no | Another account already uses that email. |
invalid_transfer |
400 | no | The transfer in the request is not usable, such as an address with no account. |
site_takes_money |
409 | no | The site takes money into a payout account, so it cannot change owner yet. |
Source, publishing and deployments
| Code | Status | Retryable | When |
|---|---|---|---|
publish_disabled |
503 | no | This deployment has no build capacity. Upload built output instead. |
no_source |
409 | no | The site has no source yet. Push to its git repository. |
no_live_version |
409 | no | Posts need a live site to build against. Publish the site. |
needs_approval |
409 | no | The owner turned on "Publishing needs approval". Nothing but a person in the app puts this site live. Send the owner the preview's review_url. |
no_live_source |
409 | no | The live version was not built from the source we hold. |
build_failed |
422 | no | The site did not build. detail holds the log. |
build_timeout |
422 | no | The build was stopped for taking too long. detail holds the log. |
invalid_redirects |
400 | no | The site's redirect rules are not usable. |
missing_asset |
409 | no | An image the site uses is gone from storage. |
invalid_manifest |
400 | no | The manifest is empty, too large, or names a path this platform does not serve. It also holds server code the plan does not run. |
no_store |
409 | no | The site has no store yet. Publish with a migration first. |
invalid_sql |
400 | no | The statement is not one this call takes, or the store refused it. The message says which. |
invalid_bookmark |
400 | no | The restore names no bookmark. |
export_pending |
503 | yes | The export is still being made. Ask again in a minute. |
runtime_disabled |
503 | no | This platform does not run the runtime API. |
invalid_mail |
400 | no | The mail has more than one address, a subject over one line, or no text. |
mail_needs_domain |
409 | no | The site has no domain verified for mail. A site sends mail only from its own domain. |
mail_limit |
429 | yes | The site sent its mails for today. |
account_mail_limit |
429 | yes | The sites of the account sent their mails for today. |
mail_paused |
409 | no | Clave or the mail service stopped the site's mail for its bounces or complaints. |
invalid_return_path |
400 | no | The return path is not a path on the site. |
no_publishable_key |
409 | no | The payout account has no publishable key for the checkout form. |
hold_expired |
409 | no | The hold on the payment ran out. Open a new payment. |
already_paid |
409 | no | The payment is paid already. |
invalid_key |
400 | no | The key is not the publishable key of that account and mode. |
invalid_staff |
400 | no | A label, a permission or the address is not one this call takes. |
identity_required |
403 | no | The call needs the Clave-Identity of a signed-in person on this site. |
draft_needed |
409 | no | A draft's secret lives on its draft, and the slot shows none. Make a draft first. |
draft_mail_recipient |
403 | no | A draft sends mail only to a person with a role on the site. |
not_on_a_draft |
403 | no | A draft's key called something a draft cannot do yet. It works on the live site. |
not_permitted |
403 | no | That person may not do this on this site. |
invalid_code |
401 | no | The sign-in code is unknown, used or expired. |
no_role |
403 | no | The person has no role on the site any more. |
invalid_call |
400 | no | The method or the path is not one a call to the site takes. |
invalid_secret |
400 | no | The name or the value is not one a secret takes, or the site holds its 20. |
turnstile_disabled |
503 | no | This platform has no spam check. |
length_required |
411 | no | The file has no Content-Length. Send it with its length, not as a stream. |
body_length |
400 | no | The file is not as long as its Content-Length said. |
unsupported_type |
415 | no | A public file is not an image, or its bytes are not the image it says. |
invalid_path |
400 | no | A path in the request is not one this platform stores. |
invalid_file |
400 | no | The uploaded bytes are not a file this platform stores. |
hash_mismatch |
400 | no | The uploaded bytes do not hash to the hash in the URL. |
size_mismatch |
400 | no | The uploaded bytes are not the length the manifest gave. |
upload_incomplete |
409 | no | Files the manifest names have not arrived. |
not_accepting_uploads |
409 | no | The deployment is past the stage that takes files. |
superseded |
409 | no | A newer deployment is live. Create a new one. |
not_restorable |
409 | no | The deployment's files are not in the archive. Deploy from source. |
unarchived_content |
409 | it depends | Files are missing from the archive. On a publish the same call fixes it; on a restore it does not. |
storage_error |
502 | yes | Storage had a temporary problem. |
invalid_slot |
400 | no | No preview slot of that number. |
Previews
| Code | Status | Retryable | When |
|---|---|---|---|
preview_disabled |
503 | no | This deployment cannot serve previews. Publish, and restore if needed. |
preview_empty |
409 | no | The slot holds no draft yet. |
preview_expired |
410 | no | The preview is marked for removal. Create a new one. |
preview_holds_work |
409 | no | The slot holds work that is not on the site. |
slot_empty |
409 | no | The slot holds no draft to act on. |
slot_not_branched |
409 | no | The slot has no work of its own, so nothing can be brought into it. |
draft_behind |
409 | no | The site moved on since this draft was built. |
draft_conflict |
409 | no | The site and the slot changed the same files. |
Domains
| Code | Status | Retryable | When |
|---|---|---|---|
invalid_domain |
400 | no | The name is not one this platform can hold. |
invalid_mode |
400 | no | The mode is not one this platform serves. |
invalid_record |
400 | no | The DNS record is not one this platform writes. |
record_rejected |
400 | no | The DNS provider refused the record as written. |
records_not_managed |
409 | no | This platform does not hold this domain's DNS. |
cname_unavailable |
503 | no | The address a CNAME points at is not ready. |
domain_taken |
409 | no | Another account holds that domain. |
domain_unavailable |
409 | no | That name is not one this platform gives out. |
domain_not_active |
409 | no | The domain is not serving yet. |
attach_blocked |
409 | no | Something about the domain stops a site going on it. |
site_has_domain |
409 | no | The site has a domain already, and a site has one. Detach it first. |
domain_not_attached |
409 | no | Mail needs a site on the domain first. |
invalid_mail_local |
400 | no | The part of the address before the @ is not one mail takes. |
mail_domain_taken |
409 | no | Mail from that domain is on already, for another site. |
mail_record_in_use |
409 | no | A DNS record stands where a mail record must go. The message names it. |
Forms
| Code | Status | Retryable | When |
|---|---|---|---|
invalid_fields |
400 | no | A field is missing, repeated, or of a kind this platform does not take. |
invalid_notify_to |
400 | no | The notify address is not a plain valid address. |
notify_to_not_on_site |
400 | no | The notify address is not one this site holds. |
Payments and payouts
| Code | Status | Retryable | When |
|---|---|---|---|
payouts_disabled |
501 | no | This deployment cannot take money for a site owner. |
payouts_unavailable |
503 | no | This deployment holds no sealing key, so it keeps no credential. |
payout_rail_unavailable |
501 | no | This deployment does not offer that way of taking money. |
invalid_payout_account |
400 | no | The payout account in the request is not usable. |
no_payout_account |
409 | no | The site takes no money yet. |
no_draft_account |
409 | no | A draft asked for a test payment, and the site has no active draft account. The owner sets up test payments on drafts. |
payout_account_pending |
409 | no | The payment provider will not let that account take money yet. |
payout_account_no_currency |
409 | no | The provider has not said what the account is priced in. |
payout_account_gone |
409 | no | The account the money went into is not used here any more. |
refund_needs_payout_owner |
403 | no | The payment is from before the site changed owner, and its money is in another account. |
payout_key_unreadable |
502 | no | This platform can no longer read the key held for that account. |
invalid_payment |
400 | no | The payment in the request is not one this site can open. |
invalid_payout_offer |
400 | no | The offer in the request is not usable. |
offer_expired |
409 | no | The offer ran out before anybody answered. |
offer_already_accepted |
409 | no | Both parties agreed already. |
not_yours_to_answer |
409 | no | This account opened the offer, so the other party answers it. |
nothing_paid |
409 | no | Nobody paid, so there is nothing to give back. |
confirm_required |
409 | no | The call changes something the caller has not confirmed. |
Billing
| Code | Status | Retryable | When |
|---|---|---|---|
billing_disabled |
501 | no | Nothing is metered on this deployment and no payment is required. |
invalid_plan |
400 | no | No plan of that name. |
plan_unavailable |
501 | no | That plan is not purchasable on this deployment. |
already_subscribed |
409 | no | The account is on that plan already. |
no_billing_history |
409 | no | The account never completed a payment, so there is no portal to open. |
paid_subscription_active |
409 | no | A paid subscription is running. Cancel it before granting a plan. |
Photos, uploads and sketches
| Code | Status | Retryable | When |
|---|---|---|---|
photos_disabled |
503 | no | No stock photo library is configured. Add the photograph to the source yourself. |
photos_busy |
503 | yes | The photo library is capping this whole platform, not you. Wait a minute. |
invalid_photo |
400 | no | No photo of that id in the library. |
uploads_disabled |
503 | no | This deployment cannot store files, or has no browser origin to hand over. |
invalid_upload |
400 | no | The upload in the request is not usable. |
stale_hash |
409 | no | The file changed while it was being stored. Send it again. |
sketches_disabled |
503 | no | This deployment cannot host design sketches. Describe the directions instead. |
invalid_sketches |
400 | no | A direction is missing something it needs. |
Analytics and feedback
| Code | Status | Retryable | When |
|---|---|---|---|
analytics_disabled |
503 | no | Analytics is off on this deployment. |
invalid_kind |
400 | no | The feedback kind is not one this platform files. |
invalid_message |
400 | no | The feedback message is missing. |
message_too_long |
400 | no | The feedback message is over the limit. |
context_too_large |
400 | no | The feedback context is over the limit. |
Nothing provider-shaped leaks
No /v1 answer names the hosting provider, its identifiers or its error
strings. A provider_error says which part of the platform failed, in
this platform's own words, and stops there.
Two answers carry a provider's address on purpose, because a payer's browser has to land on it: a checkout URL and a portal URL.
A message does not blame the wrong party
Clave works to keep each message about the party that can act on it. A refusal names the consequence and the remedy for the surface you are on.
If a message sends you to fix something you did not break, that is a
defect here. Send it with POST /v1/feedback.