dropto.run docs
Reference

Error codes

Errors follow RFC 9457. The type field carries one of the codes below, and a client should branch on that rather than on the status or the wording:

JSONCode
{ "type": "quota_exceeded", "title": "Too many files", "status": 400, "detail": "This deploy has 2,400 files, and the limit is 2,000 per deploy.", "limit": 2000, "actual": 2400 }

limit and actual appear on quota refusals. detail is written to be shown to a person.

quota_exceeded

A quota from the plans table was exceeded. Carries limit and actual extensions.

address_quota_exceeded

The per-network daily limit on creating sites without an account was exceeded. Carries the same extensions as QuotaExceeded.

A separate code because the remedy is different, and the frontend has to say so: every other quota is about what the caller has used, while this one is about what everybody sharing their address has used, and signing in removes it entirely rather than raising it.

unsafe_path

A manifest entry was refused: its path failed PathSafety.Normalize, its checksum is not a lowercase hex SHA-256, or its size is negative or 0 for content that is not empty. Carries a path extension.

missing_index

The manifest has no index.html at its root.

duplicate_path

Two different entries normalize to the same path.

verify_failed

The stored objects do not match the manifest (missing file or size mismatch).

invalid_state

The deploy is in a state that does not allow the requested operation.

deploy_expired

The deploy's files have already been removed by retention, so it can no longer be made live.

Its own code rather than InvalidState because the remedy differs and the frontend has to say so: no retry brings an expired version back, so the only offer is publishing again.

subdomain_reserved

The subdomain is in the ReservedNames blocklist.

subdomain_invalid

A user-typed subdomain failed SubdomainFormat.Normalize.

subdomain_taken

A user-typed subdomain is already used by another site.

Its own code rather than SubdomainReserved: the blocklist is about a name nobody may ever have, while this one is about a name somebody already does, and the two read very differently to a person choosing what to type next.

invalid_request

The request body is missing a field the endpoint cannot supply a default for.

Used where a silent default would be a decision rather than an absence — a settings update whose body omits the setting would otherwise read as "turn it off", which is the opposite of what a client sending a malformed body meant.

site_not_found

The site does not exist, or does not belong to the caller.

site_suspended

An administrator has taken this site down, so its owner cannot put it back on the air.

Its own code rather than Forbidden for the reason PaymentPastDue is: nothing is wrong with the request or with who is making it, and there is no version of it that would succeed. The frontend needs to say what happened and point at support, which it cannot do from a generic refusal. Returned only to the site's owner, and only where they are trying to change the suspension: resuming the site, or removing it. It is never the answer to a request to read a site — a suspended site is still the owner's, and hiding it behind SiteNotFound would leave them unable to see why their pages stopped serving. Removal is refused for a different reason than resuming, and the shared code is deliberate: both are "this is not yours to change while the suspension stands", and the frontend has one thing to say about either. What removal specifically protects is the files — deleting them is the one irreversible act here, and a takedown that turns out to be wrong needs something to restore.

password_protection_unavailable

The caller's plan does not include password-protecting a site.

Distinct from Forbidden on purpose: nothing is wrong with the caller or the site, and the fix is a different plan rather than a different request. The frontend uses the difference to offer an upgrade instead of reporting an error. Also returned for an anonymous site, whose plan never carries the flag. That is not an upgrade prompt but a signal to sign in first: an anonymous site is held by one browser's claim token, so a password would be a second thing to lose with no recovery from losing either.

password_too_short

The submitted site password is too short.

Length is the only rule, so this is the only way a password can be rejected. There is deliberately no code for "needs an uppercase letter" — see SitePasswords.MinimumLength.

invited_viewers_unavailable

The caller's plan does not include sites only invited people may see.

The same kind of refusal as PasswordProtectionUnavailable: the fix is a different plan, so the frontend offers one instead of reporting an error. Only switching the mode on and adding people are gated — switching it off and removing people never are, so a site whose plan lost the feature is never stranded behind a list its owner cannot shorten.

feedback_unavailable

The caller's plan does not include comments on a site's pages.

The same kind of refusal as InvitedViewersUnavailable, with the same asymmetry: choosing who may comment and giving somebody the comment role are gated, turning comments off never is.

feedback_off

Comments are not open to this person on this site right now.

One code for every reason — comments switched off, the plan lost them, the site left invite-only, the person was taken off the list or given the view role — because the overlay does the same thing for all of them: it hides itself. Telling a script on the customer's page which of these it was would tell that page's own JavaScript who is on the list.

comment_not_found

The thread or comment does not exist on this site.

Also the answer for an id that exists on another site, so the two cannot be told apart.

site_comments_full

The site holds as many comments as its plan allows. Carries limit and actual.

comment_rate_limited

This person has written too many comments on this site in the last minute or day. Answered 429.

A guard against a script or a stuck key, not a price: it is the same on every plan. Carries limit and window.

comments_busy

Too many changes to this site's comments are being written at once; nothing was written. Answered 503.

Writes to one site's comments go one at a time, and a write that has waited its turn for a few seconds gives up rather than hold a database connection every other site needs. Carries retry_after in seconds: sending the same change again then is safe.

rate_limited

A rate limit refused the request: too many of this kind from this caller in the current window. Answered 429 with a Retry-After header.

Carries retryAfter (seconds, the same figure as the header) and policy, the name of the limit — for example deploy-write or sign-in-password. A signed-in caller is counted on their own; an anonymous one shares the count with everybody behind the same address.

site_viewers_full

Adding these addresses would take a site's list past the plan's ceiling, so none were added.

All or nothing: inviting the first four of six and dropping two leaves the owner to work out which two. Carries limit and actual (the count the list would have reached) the way QuotaExceeded does, so the frontend can say how many more fit.

site_viewer_not_found

The address is not on this site's list.

site_viewer_is_owner

The address is the one added for whoever switched the site to invite-only, and cannot be removed.

Removing it would lock that person out of their own site: the edge knows who holds a cookie, not who owns the site.

proxy_unavailable

Proxying /api/* to an upstream is not switched on for this deployment (ApiProxy:Enabled), so there is nothing to declare yet.

upstream_not_found

No upstream with that id on this site.

upstream_exists

The site already declares an upstream for that origin; change that one instead.

upstream_invalid

The upstream was refused: the origin is not https://host on a public name outside the platform's own domains, a header name is one an owner may not set, a value would split a header line, a method is not one of GET, POST, PUT, PATCH, DELETE — or the site's upstreams would exceed what the edge stores. The detail says which.

upstreams_full

The site already declares the most upstreams it may. Carries limit and actual.

upstream_blocked

The platform has stopped proxying to that origin for every site, so it cannot be declared. Contact support to ask why.

functions_unavailable

Running functions is not switched on for this deployment (Functions:Enabled), so there are no variables to set yet.

functions_not_allowed

This site may not run functions — its plan has none, it belongs to no account, or the platform switched its functions off — so a variable cannot be set on it. Removing one is always allowed.

env_var_not_found

No variable with that name on this site.

env_var_invalid

The variable was refused: the name is not uppercase letters, digits and underscores starting with a letter or underscore, at most 64 characters, or the value is empty or over 5 KB. The detail says which.

env_full

The site already holds the most variables it may, or their values together would exceed 40 KB. Carries limit and actual.

site_viewer_recently_emailed

The invitation to this address was mailed less than a day ago, so it is not sent again yet.

Without the limit, a list of a hundred and a "send again" button would be a way to mail a hundred strangers on demand under the dropto.run name. Carries retryAfter, the instant it may be sent again, so the dashboard can say when.

site_invitations_daily_limit

Sending these invitations would take the site past the mail it may send in a day, so none were sent.

The ceiling is the plan's list size, per site, over the last 24 hours — a site can mail as many people in a day as it may list. Carries limit and actual like QuotaExceeded. The addresses can still be added without mail (notify: false).

analytics_unavailable

The caller's plan does not include the traffic figures for a site.

The same kind of refusal as PasswordProtectionUnavailable and distinct from Forbidden for the same reason: the request is well-formed and the site is the caller's own, so the frontend should say which plans include it rather than report an error. The site page normally never provokes this — GET /sites/{siteId} reports the plan's window, and the panel says what the plan includes without asking. It exists for the request made anyway: a stale tab open while a plan changed, or a direct call.

analytics_range_unavailable

The traffic range asked for is longer than the site's plan includes.

Refused rather than clamped, unlike the first traffic endpoint did: the range decides every number on the page, and a page that silently answered a shorter range than its label would be wrong in a way nobody notices. The site detail carries the plan's window, so the page only offers ranges it can have; this answers a stale tab or a direct call.

analytics_dimension_unavailable

The traffic breakdown, filter or audience asked for needs a plan above the site's.

Top lists and filters need the breakdown capability; any audience but people needs the detail one. A 403 with its own code, like AnalyticsUnavailable, so the page can name the tier that includes it instead of reporting a fault. The lists are computed only on request, so this check is the gate — a locked panel in the page is presentation.

scheduled_expiry_unavailable

The caller's plan does not include scheduling when a site comes down.

The same kind of refusal as PasswordProtectionUnavailable: the request is well-formed and the site is the caller's own, so the frontend should offer an upgrade rather than report a fault. Also returned for a site with no account, and there the meaning is different in a way worth stating: such a site already has an expiry the platform set from its tier's lifetime, and letting the claim-token holder move it would be free permanent hosting for anybody who reads the API. Signing in and claiming the site is the fix, not a different plan.

browser_edit_unavailable

The caller's plan does not include editing a file in the browser.

The same kind of refusal as PasswordProtectionUnavailable: well-formed request, the caller's own site, and the fix is a different plan rather than a different request. Unlike the analytics export, this one really does withhold something — the endpoint it guards is the only way a file's contents leave the control plane — so it is returned by the server rather than merely reflected in a hidden button.

custom_domains_unavailable

The caller's plan does not include custom domains, or the site already has as many as it allows.

The same kind of refusal as PasswordProtectionUnavailable: the request is well-formed and the site is the caller's own, so the frontend should offer an upgrade rather than report a fault. One code covers both "the tier has none" and "the tier is full", with the limit and the current count carried as extensions the way QuotaExceeded does — two codes would have the frontend branch on a difference the customer does not experience. Every built-in tier is seeded at zero, so this is what the feature answers with until a plan row raises the column.

hostname_invalid

The submitted hostname is not one this platform can serve.

Everything CustomHostnameFormat.Normalize refuses arrives here, and the reasons are deliberately not split into separate codes: a bare apex, a wildcard, an over-long label and an emoji are all "this is not a hostname we can put a certificate on", and the customer's next action is the same in every case — type a different one. The detail carries which rule failed, in words. A bare domain — example.com, or one whose own nameservers answer for it such as example.com.vn — also carries suggestedHostname, the www name to add instead. Still this code rather than one of its own, so a client that ignores the extension shows an ordinary error; a client that reads it can offer the suggestion as one action. It also covers a hostname on our own domains, which is refused for a reason that is not about shape at all: serving customer content there would break invariant I1. That distinction matters to us and not to the person typing, so it does not get its own code either.

hostname_taken

Another site already holds the submitted hostname.

Distinct from HostnameInvalid because the hostname is fine and the answer is not "type a different one" — it is that somebody has to give this one up first. The edge itself permits several accounts to hold one hostname and routes by CNAME, so there is no takeover to prevent here; what this stops is two of our own sites claiming one name and the second write silently losing. A hostname whose row was given up is claimable again, which is why the unique index excludes removed rows: otherwise an abandoned attempt would hold a name against its real owner for good.

domain_not_found

No custom domain of this site has the given id.

Returned rather than a bare 404 so the frontend can tell it apart from SiteNotFound — a stale tab acting on a domain somebody else in the account removed should say so, not imply the site is gone.

domain_checked_too_recently

The custom domain was checked too recently to be checked again.

The manual re-check exists because waiting out an hour of backoff after adding the DNS records is a bad experience, and it needs its own limit for the same reason the backoff exists: the edge provider's API budget is counted per account and shared with the edge writes on the deploy path, so a button anyone can hold down is a way to break deploys.

forbidden

The caller has no permission on this resource.

no_account

The request names no account the caller may act on.

Its own code rather than Forbidden because the dashboard can fix it without bothering anybody. It means one of three things — nobody is signed in, the account chosen in the switcher is one the caller has been removed from, or the header named an account they were never in — and the second is the ordinary one: a choice kept in localStorage outlives the membership it refers to. Seeing this, the dashboard clears that choice and retries on the caller's own account, which is what somebody removed from a team should land on. Deliberately one code for all three. Distinguishing "you were removed" from "that account does not exist" would let anybody test account ids for existence by sending them here.

owner_only

The caller belongs to the account but holds a member's seat, and this action needs an owner's.

Distinct from NoAccount because the two have opposite remedies. That one is fixed by signing in or picking a different account; this one cannot be fixed by the person who hit it at all — somebody else has to act, or change their role. A dashboard that answered this with a sign-in prompt would send a member round a loop that changes nothing. Distinct from Forbidden so the console can hide the control before it is pressed. Hiding a button is never the permission — the server refuses regardless — but a member who can see a delete button they may not use will press it, and being told afterwards is the worst moment to learn it.

no_seats_left

The account has no seat free for another person.

Its own code because the two remedies are different and the dashboard has to offer the right one: a tier that sells no seats at all needs a different plan, while a tier that sells them and has none spare needs more seats bought. Both are refusals to invite, and telling them apart in prose alone would leave the console guessing. Counted across members and invitations still standing, because an invitation nobody has accepted yet is a seat already promised. Without that, sending five invitations for one free seat is a race the owner did not know they had started.

already_member

The address named is already on the account.

invite_not_usable

The invitation cannot be accepted: expired, withdrawn, already used, or meant for somebody else.

One code for four situations, deliberately. Telling them apart would answer questions about an account the caller has no relationship with — whether an address was invited, and whether a colleague has already joined — to whoever is holding the link. The link is the only thing that proves anything here, and it proves one thing or nothing.

invalid_credential

A Google One Tap credential failed to verify: bad signature, wrong audience, or expired.

email_in_use

A Google sign-in reached an address another account already holds, and could not be linked to it.

Either a second Google account with the same address, or a Google account whose address Google itself has not verified. Linking in either case would let somebody join an account they have not proven is theirs.

wrong_email_or_password

The email and password do not match an account.

One code for "no such address" and "wrong password", deliberately: telling them apart tells a stranger which addresses have accounts here. The same code answers an account that has no password at all — a Google-only one — for the same reason.

email_invalid

The address typed is not one a link could be sent to.

email_disposable

The address belongs to a disposable-mail service, which sign-up refuses.

password_rejected

The password breaks one or more of the rules. Carries a rules extension naming them.

The names are Domain.PasswordPolicy's, which the page ticks against, so a refusal can point at the exact rule rather than repeating all of them.

wrong_password

The current password given while changing a password is not this account's.

Its own code rather than WrongEmailOrPassword, because the reason that one is vague does not apply here: the caller is already signed in as this account, so nothing is revealed by saying which of the two fields was wrong, and the page can put the message on the right field.

password_not_set

A password was asked to change on an account that has never had one.

The remedy is a different act, not a corrected field: a Google-only account sets its first password through the mailed reset link, because the mailbox is the only proof available when there is no current password to ask for.

last_sign_in_method

Removing a way to sign in would leave the account with none (IMPLEMENTATION-PLAN §21.1).

Answered rather than prevented only in the UI, because the button is hidden in the state that produces it and a request that arrives anyway is either a stale page or somebody calling the API directly. Locking somebody out of their own account is not a thing to leave to the client.

email_unchanged

The address given for a change of address is the one the account already uses.

A confirmation or reset link has expired, was already used, or never existed.

One code for all three, like InviteNotUsable: from the reader's chair they are the same event, and the remedy — ask for a new link — is the same too.

turnstile_required

Several sign-ins failed from here or for this address, so the request has to carry a bot check.

Tells the page to show the Turnstile widget and try again. Never a lock: an account cannot be locked by somebody who merely knows its address.

turnstile_failed

The bot check the request carried did not verify.

sign_up_unavailable

Creating an account with a password is not available on this deployment, because its bot check is not configured.

Refusing is the safe side: a sign-up with no bot check is a form anybody can script, and a missing setting should not quietly open one.

user_not_found

No account has that id.

Admin-only, and unlike SiteNotFound it never stands in for "not yours": the admin API is not scoped to a caller, so a miss here really is a missing row rather than a hidden one.

plan_not_found

No plan has that id.

announcement_not_found

No announcement bar has that id. Admin-only.

abuse_report_not_found

No abuse report has that id.

invalid_signature

A payment webhook's signature did not match, so the delivery was refused.

Nothing is written when this is returned. A request that cannot be shown to have come from the provider must not be able to create a row, or the event table becomes something anyone can fill with invented deliveries.

invalid_payload

A payment webhook was signed correctly but its body could not be read.

unmapped_event

The webhook is a type this system does not act on.

An ordinary outcome rather than a fault: providers emit far more event types than are mapped, and the unmapped ones are still stored whole. It is an error code only because mapping returns a Result, and the caller answers the provider with success either way — a non-2xx would make them retry an event forever that will never be acted on.

plan_not_purchasable

The plan and interval asked for do not correspond to anything the provider can sell.

Covers a plan that is not purchasable, and a plan and interval pair with no product configured at the provider. Refused rather than substituted: picking a neighbouring product would charge the customer for something other than what they chose.

plan_not_offered

The plan asked for is sold to named customers, and the account asking is not one of them.

Some plans are agreed with particular customers rather than listed, and only those customers can buy them. Picking a different billing interval will not help; ask us about the plan instead. A subscription already running on such a plan is unaffected by this, and renews as normal.

plan_is_public

A listed plan was named where one agreed with particular customers was expected.

A plan everybody can already buy cannot also be offered to one customer in particular. Refused rather than accepted as a harmless no-op, because a row saying so would read as a restriction that is not being applied — and anybody later auditing who may buy what would have to know that some of the rows mean nothing.

plan_downgrade_not_allowed

The plan and interval asked for cost less than what the subscription is already paying, and a subscription is not moved downwards.

Its own code rather than InvalidState because the remedy is a different act, and the frontend has to name it: there is no timing that makes this change happen, so the only way to a cheaper tier is to cancel and buy it once the paid period has run out. Keyed on the price of the pair rather than on which tier is "higher", because the price is what decides whether the provider would settle the move as a refund.

plan_not_grantable

The tier has a price, so it cannot be handed to an account by an administrator.

A priced tier is something a customer pays for, and POST /admin/users/{id}/plan takes no money and creates no subscription: granting one would serve a paid tier that is never billed, for as long as nobody notices. Keyed on the tier having a price rather than on is_purchasable, because a tier that is priced but not yet on sale is still one somebody else pays for — reading the sale flag instead would leave exactly those free to give away.

subscription_in_place

The account already has a live subscription, so its tier is not an administrator's to set.

What a subscriber is served follows what they are charged: a renewal restamps the account from the product the provider billed, so a tier assigned by hand survives only until the next renewal and then reverts with nothing to say it did. Refused rather than quietly overwritten — the subscription has to be cancelled or moved first, which are the two calls that also tell the provider.

subscription_not_found

No subscription matches that provider and provider-side id.

From a webhook this usually means an earlier event was lost or arrived out of order. Only an activation is allowed to create a subscription, so anything else naming an unknown one is refused rather than invented — a renewal cannot say which plan was bought.

invoice_not_found

No invoice matches that provider and provider-side id.

payment_event_not_found

No stored webhook has the given id.

task_not_found

No task in the work queue has the given id. Admin-only.

provider_unavailable

The payment provider could not be reached at all.

Distinct from ProviderRefused because the remedy differs: this one is worth retrying in a moment, and the frontend can say so.

provider_refused

The payment provider was reached and said no.

Deliberately does not carry their wording. Their message is written for us and can name amounts, customer ids or internal reasons that have no business reaching a browser — it is logged instead, with the trace id their support asks for.

provider_busy

The provider is still finishing an earlier change to the same subscription.

Its own code rather than ProviderRefused because the remedy differs, which is the only thing that has ever justified a code here: this one clears itself in about a minute and the answer is to wait, while a plain refusal means the request as sent will never be accepted. Told apart by the machine-readable code in the provider's response, never by its prose.

payment_past_due

A payment on this account has failed and has not been settled, so it may not publish.

Its own code rather than Forbidden because the remedy is unlike any other refusal here: nothing is wrong with the request, the account, or the permission — a card needs replacing, and the frontend has somewhere to send the customer to do it. What it refuses is publishing, never serving. Sites already on the air stay on the air for the whole grace window; what stops is putting anything new there.

payment_disputed

A payment on this account has been charged back, so it may not publish while that stands.

Distinct from PaymentPastDue because the remedy is not a card: the money has been pulled back by the customer's bank, and there is no self-service page that undoes that. The frontend has to send them to a person rather than to a payment form, which is the whole reason this is not the same code with different words. Like its neighbour, it refuses publishing and never serving.

invalid_token

A personal access token was presented and does not work: unknown, revoked, expired, or belonging to a blocked account.

Distinct from Forbidden, and the distinction is the point rather than tidiness. Every endpoint here checks ICurrentUser and answers 403, so a failed credential would otherwise be indistinguishable from "signed in, but not yours" — and the two need opposite responses from a CLI. One means run login again; the other means the account genuinely cannot do that. Answering 403 to a revoked token would leave a person re-running a command that will never work, with nothing telling them their token is the problem.

token_not_found

No access token with that id belongs to the signed-in account.

Returned for an id that exists under another account as well as for one that exists nowhere. The two are the same answer on purpose: distinguishing them would confirm that a guessed id is somebody's real token.

capacity_hold

Publishing is being held back platform-wide, not because of anything this caller did.

The only refusal here that is about us rather than about the request, which is why it is a separate code and a 503 rather than one more quota_exceeded. A client that could not tell them apart would show "you have reached your limit" to somebody who has not, and the honest answer — wait and try again — would never reach them. Never returned to a paid account. That is a promise on the pricing page and a chargeback if it is broken.

cloudflare_busy

The platform's Cloudflare API budget is spent for a few minutes, so a change that has to reach Cloudflare right away was not started.

A 503 with a Retry-After, returned only by the custom domain routes that call Cloudflare inside the request (adding a domain, asking for a re-check). Refused before anything is written, so trying again later meets the same request from the start rather than half of one. About us, not the caller: nothing the caller did spent the budget, and waiting is the whole of the answer.

invalid_grant

A command-line sign-in code could not be exchanged.

One code for four situations — the code never existed, it expired, it was already spent, or the verifier does not match it — because separating them would tell whoever is probing the exchange which of their guesses was closest. The remedy is the same in all four: run the sign-in again. Distinct from InvalidToken, which is about a credential that was presented and rejected. This is about the five-minute code that would have produced one, so a CLI reads it as "that attempt is dead, start over" rather than "your stored token is bad".

authorization_pending

A device sign-in exists and is still waiting for somebody to approve it.

Not a failure, despite the status: it is the ordinary answer for most of a device sign-in's life, and it means "ask again in a moment". Distinct from InvalidGrant because the two lead a command line to opposite behaviour — keep polling, or stop and tell the person it is over. The name is the one RFC 8628 gives, so a client written against that specification reads it already.

slow_down

A device sign-in is being polled faster than the interval it was given.

Also not a failure: the request is alive, and the answer is the same as AuthorizationPending plus "more slowly". Its own code because a client that cannot tell the two apart has no way to learn it is the problem, and the usual response to an unexplained refusal in a polling loop is to poll harder.

forms_not_available

Form submissions were switched on for a site whose tier does not carry them.

Its own code rather than Forbidden, because the remedy is unlike any other refusal on a site setting: nothing about this request or this caller is wrong, and no permission would change the answer. The account is on a tier that does not include forms, so the only way past it is a different tier — which is a link the dashboard can offer, and only if it can tell this apart from "you may not touch this site".

site_folder_not_found

No such folder in the caller's account, or one belonging to somebody else.

Deliberately one code for both, the same choice SiteNotFound makes: telling them apart would let anybody probe which folder ids exist, and a folder id names how another account has arranged its work. It is also the answer to filing a site into a folder of somebody else's, which is a 404 rather than a 403 for the same reason — a caller who may touch the site but not the folder learns nothing about whether the folder is real.

site_folder_name_taken

A folder in the same parent already has that name, ignoring case.

Scoped to the parent and not to the account: two folders called "Drafts" under two different parents are two different places and are allowed. The refusal names the collision rather than the rule, because "Drafts already exists here" is actionable and "names must be unique" is not.

site_folder_name_invalid

A folder name was empty, or cleaned away to nothing.

Its own code rather than InvalidRequest because the frontend puts this one beside the field somebody is typing in, and a generic code would have it guess which field.

site_folder_cycle

A move would have put a folder inside its own subtree.

Worth its own code because the dashboard can prevent it entirely — it knows the tree and can refuse the drop before it happens — and needs to tell this apart from a move that failed for a reason it could not have predicted.

site_folder_too_deep

A folder, or something under it, would have nested deeper than the tree allows.

Carries limit as an extension, because the refusal is about a number and a message that does not say the number leaves somebody dragging a folder around trying to find the level that works.

promotion_not_available

The promotion asked for cannot be claimed by this account right now.

Several situations answer with this one code and the response does not say which: the promotion may have closed, it may not have opened yet, its places may be gone, it may have been withdrawn, or it may be one offered to particular accounts rather than to everybody. Retrying will not help in any of them, and neither will a different account. If you were given this code by us and believe it should still work, get in touch and we will tell you what happened to it.

promotion_already_redeemed

This account has already claimed this promotion.

Nothing has gone wrong and nothing needs doing: what the promotion gives is already on the account, and claiming it a second time would not add to it. A promotion can be claimed once per account, so this is also the answer to a request that was sent twice — by a retry, or by two clicks — rather than a sign that the first one failed.

two_factor_required

The account being acted on requires two-factor sign-in, and the caller has not turned it on.

The account's owner turned the requirement on. Turn on two-factor from your profile and the account opens again. Your other accounts are not affected, and access tokens are never asked for a code. The body carries accountId, accountName, ownerName and requiredAt.

two_factor_invalid_code

The six-digit code does not match, or has already been used.

Codes change every 30 seconds and each works once. Use the one your app is showing now. During sign-in the body carries attemptsLeft, the tries left before a pause.

two_factor_locked

Too many wrong codes: sign-in with this account is paused.

The pause applies on every browser and ends at lockedUntil, in the body. If you did not try to sign in just now, someone else has your password; change it once you are back in.

two_factor_challenge_expired

The code step of a sign-in has expired, or this browser has none waiting.

The step lasts five minutes after the password or Google. Sign in again to get a new one.

two_factor_already_enabled

Two-factor sign-in is already on for this person.

Turn it off first to set it up again with another app.

two_factor_not_enabled

Two-factor sign-in is not on for this person, so there is nothing to change.

Also returned to an owner who asks to require two-factor of an account's members before turning it on for themselves, since the requirement would lock them out first.

two_factor_owner_enforcing

Two-factor cannot be turned off while an account you own requires it.

The requirement would then lock you out of your own account. Stop requiring it on that account's members page first. The body carries accountName.

recovery_code_invalid

The recovery code is not one of your unused codes.

Each code works once, and making a new set cancels every code in the old one. Case and dashes do not matter.

trusted_device_not_found

No trusted browser with that id is on your list.

It may already have been forgotten, or have expired after thirty days.

account_deletion_pending

This account is scheduled for deletion, so it cannot be signed in to.

Answered on every way in during the grace period. The deletion can still be called off with the link in the mail sent when it was requested. The body carries deletionDueAt.

sole_owner_with_members

You are the only owner of an account that still has other members, so deleting yourself would leave them in an account nobody owns.

Make one of them an owner on that account's members page, or remove them, then try again. Nothing was changed. The body carries accountName.

founder_with_members

You created an account that still has other people on it, so you cannot delete yourself yet.

The account's plan is held in its creator's name, so it cannot be left behind without them. Remove everybody else from that account on its members page, then try again — deleting yourself then deletes the account with you. Nothing was changed. The body carries accountName.

deletion_not_confirmed

The address typed to confirm a deletion is not the one this account signs in with.

support_report_unavailable

Reporting a problem without signing in is not available on this deployment, because its bot check is not configured.

Sign in and report it from there, or write to the address the message names. Refusing is the safe side for the reason SignUpUnavailable gives: a form anybody can script is a way to fill the team's chat.

support_report_not_found

No support report has that id.

Last modified on