API reference

Errors

Every failure comes back as JSON with the same shape. Switch on error.code, never on the message — codes are stable, messages are written for people and can change.

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": {
      "fields": {
        "destinationUrl": [
          "Must be a valid http or https URL"
        ]
      }
    }
  }
}

Which failures are worth retrying

Retry a 429 after backing off, and a 5xx once or twice. Never retry any other 4xx — the request is wrong, so sending it again cannot work and only spends your rate limit.

Authentication

UNAUTHORIZED401

The request carried no credentials we could read.

Why: There was no Authorization header, or it did not start with `Bearer `. A common cause is sending the key as a query parameter or a cookie — neither is accepted.

Fix: Send `Authorization: Bearer lnk_live_...` as a request header.

INVALID_API_KEY401

That key is not one of ours, or it is not this key.

Why: The key does not exist, the secret is wrong, or the key failed its checksum. All three answer identically on purpose: telling them apart would let someone probe which keys exist.

Fix: Check for a copy-paste error — a truncated key and a wrong key look the same from here. If you cannot find the key, create a new one; we store only a one-way hash and cannot recover a secret.

INVALID_TOKEN401

This OAuth access token is expired, revoked, or unknown.

Why: Access tokens last one hour. A token also dies the moment the user disconnects your app, which can happen at any time and without warning.

Fix: Exchange your refresh token at `/api/oauth/token`. If that is refused too, the user has disconnected you — send them through the consent screen again.

API_KEY_EXPIRED401

This key passed the expiry date set when it was created.

Why: The key had an expiry and that date has now passed.

Fix: Create a replacement key. Next time, roll the key before it expires — a roll gives you the new secret immediately and keeps the old one working for a grace period, so nothing goes down.

API_KEY_REVOKED401

This key was revoked and will never work again.

Why: Somebody revoked it in the dashboard, or it was revoked automatically because it was found in a public code repository.

Fix: Create a new key. If it was revoked for being public, remove it from your repository history as well — not just the latest commit — and put the replacement in an environment variable.

Permissions

INSUFFICIENT_PERMISSION403

This key is valid, but it was not given this permission.

Why: Permissions are per key and checked per request. The message names the exact one that is missing.

Fix: Edit the key in the dashboard and tick the named permission, or use a key that already has it.

INSUFFICIENT_SCOPE403

The user did not grant your app this permission.

Why: A user can untick individual permissions on the consent screen, so a scope you requested is not necessarily a scope you were given.

Fix: Read the `scope` field returned with the token rather than assuming you got what you asked for. Send the user back through the consent screen if you genuinely need the extra permission.

OUT_OF_SCOPE403

This key is limited to certain resources, and this is not one.

Why: The key has a resource scope — a set of domains, a set of tags, or both. Domains and tags are combined, so a key scoped to both may only touch links matching both.

Fix: Use a key without that restriction, or widen the scope in the dashboard. The message names the current scope.

IP_NOT_ALLOWED403

This key may only be used from specific IP addresses.

Why: Either your address is not on the key's allow-list, or we could not read your address at all. The second case is also a refusal: a restriction that stops applying when the address is unreadable is not a restriction.

Fix: Add the calling server's address to the key's allow-list, or use a key with no IP restriction. Note that your outbound address is often not the one you expect on serverless platforms.

OUTSIDE_ALLOWED_HOURS403

This key may only be used during set hours.

Why: The key has a schedule. The window is evaluated in the timezone stored on the key, which is not necessarily yours or the server's.

Fix: Wait for the window, or change the schedule in the dashboard. The message names the window and its timezone.

API_KEY_PAUSED403

This key is paused, so every request is refused.

Why: Somebody paused it, or we paused it automatically because it was used from a different continent than usual. The second is a heuristic and it does produce false positives — a new server, a VPN, a trip.

Fix: Resume the key in the dashboard if you recognise the activity. If you do not, roll it instead: that issues a new secret and stops the old one.

SESSION_REQUIRED403

This endpoint cannot be used with an API key at all.

Why: Key management, billing and the OAuth endpoints are session-only. A key that could create keys could issue itself full access and outlive its own revocation.

Fix: Do this in the dashboard while signed in.

FORBIDDEN403

Your role in this workspace does not allow this action.

Why: Some actions are limited to owners and admins. A key authenticates as the workspace, so this usually means a session with a viewer or member role.

Fix: Ask an owner or admin of the workspace to do it, or to change your role.

WORKSPACE_ACCESS_DENIED403

You are not a member of the workspace you named.

Why: The `X-Workspace-Id` header named a workspace you do not belong to.

Fix: Drop the header to use your default workspace, or send an id you are actually a member of.

AWAITING_APPROVAL409

This page is waiting for approval.

Why: The workspace requires a review before anything is published. The message names what is still outstanding.

Fix: Ask a reviewer to approve the page, or turn the approval requirement off in the workspace settings if it is not wanted.

NOT_CLONABLE404

This page cannot be cloned.

Why: Its owner never listed it as clonable, or it has since been withdrawn from the gallery. Cloning is opt-in, not the default.

Fix: Only gallery pages whose owner opted in can be cloned. Build from a template instead.

FIELDS_LOCKED403

An administrator locked some of the fields you tried to change.

Why: Locked fields are resolved at render time from a shared template, so a change here would be silently overwritten on the next render anyway. Refusing is more honest than accepting and discarding it.

Fix: The message names the locked fields. Change them on the template itself, or ask an admin to unlock them.

Validation

VALIDATION_ERROR400

One or more fields in the request body were not acceptable.

Why: A field is missing, the wrong type, or outside its allowed range.

Fix: Read `error.details.fields`. It is keyed by field name and names the problem with each one.

Extra detail: `fields`: an object mapping each bad field name to its problems.

INVALID_JSON400

The request body was not valid JSON.

Why: Usually a trailing comma, a single-quoted string, or a body sent with no `Content-Type: application/json` header at all.

Fix: Serialise the body with a real JSON encoder rather than string concatenation.

UNSAFE_DESTINATION422

We will not shorten that destination.

Why: The URL uses a scheme we do not allow, points at a private network address, or matched a malware or phishing list.

Fix: Use a public `http:` or `https:` URL. If you believe the block is wrong, contact support with the exact URL.

SLUG_UNAVAILABLE409

That short link already exists on that domain.

Why: Slugs are unique per domain, not globally. The same slug can exist on two different domains.

Fix: Pick another slug, or omit `slug` entirely and we will generate an unused one.

BARCODE_FAILED400

The barcode could not be produced from those parameters.

Why: Each symbology has strict rules about length, character set and check digits. EAN-13 needs exactly thirteen digits; Code 39 refuses lowercase.

Fix: Read the message — it names the rule that failed — and correct the data or pick a symbology that can encode it.

INVALID_PARAMS400

The barcode parameters are not usable.

Why: Either no symbology was named, or the data contains characters the named symbology has no way to encode.

Fix: Name a supported symbology and check the data against its character set and length rules.

NO_TITLE400

This collection needs a title before it can be created.

Why: The title field was missing, or present but empty. A whitespace-only value counts as empty.

Fix: Send a title with at least one non-whitespace character in it.

SLUG_TAKEN409

That page slug is already in use.

Why: Page slugs are unique within a workspace. An archived page still holds its slug, which is the case people do not expect.

Fix: Pick a different slug, or delete the page holding it. Archiving is not enough to free one.

INVALID_TRANSITION409

That approval action does not apply to the page's current state.

Why: Approval moves through fixed states in order. You cannot approve a page that was never submitted, or submit one that is already approved.

Fix: The message names the current state. Pick the action that follows it, or read the page's approval history to see how it got there.

PACK_NOT_READY400

The template pack is not complete enough to publish.

Why: A pack needs a name and at least one template before anybody could usefully install it. The message names which of those is missing.

Fix: Fill in what the message names, then publish again. The pack is saved as a draft in the meantime.

TEST_RUNNING409

An A/B test is already running on this page.

Why: Two overlapping tests split the same traffic, and neither result can then be attributed to either change.

Fix: End the current test first. Its results stay available after it ends, so nothing is lost by stopping it.

UTM_CONVENTION422

The UTM values break this workspace's naming convention.

Why: The workspace enforces a convention so campaign reporting stays consistent across everyone using it. The message names the rule that failed.

Fix: Follow the convention, or relax it in the workspace settings if it is getting in the way more than it helps.

BAD_URL400

That is not a valid web address.

Why: The value could not be parsed as a URL at all — usually a missing scheme or a stray space.

Fix: Send a complete URL including the https:// prefix, and trim any surrounding whitespace.

BLOCKED_HOST400

We will not fetch that address.

Why: It resolves to a private, loopback or link-local address, or it does not resolve at all. Fetching those would let a request reach networks it has no business in — including our own internal ones.

Fix: Use a publicly reachable address. A tunnel service works if you need to expose something running locally.

FETCH_FAILED400

The page did not answer successfully.

Why: It returned an error status, which the message includes. A 403 usually means a bot filter; a 404 usually means a typo.

Fix: Open the address in a private browser window without signing in. If it fails there too, that is what we are seeing.

NOTHING_TO_IMPORT422

We found no links on that page.

Why: The page builds its links in the browser after load. We read the HTML exactly as the server sends it, and there were no links in it yet.

Fix: Add the links by hand, or import from a page that renders its links server-side.

ENDPOINT_PAUSED409

The endpoint is paused, so a replay would go nowhere.

Why: An endpoint pauses itself after repeated failures. Replaying into a paused endpoint would fail again and count as another failure, pushing it further from recovery.

Fix: Send a test ping to confirm the receiver is healthy, switch the endpoint back on, then replay.

CONFLICT409

That affiliate is already enrolled on this page.

Why: A second enrolment would split their attribution across two rows, so neither would show their real earnings.

Fix: Nothing to do — they are already enrolled and their existing referral code still works. Update the existing enrolment if you meant to change its terms.

OWNS_SHARED_WORKSPACE409

You own a workspace that other people are in.

Why: Deleting the account would take the workspace and everyone else's work with it. We will not do that on one person's say-so.

Fix: Transfer ownership of the workspace named in the message to another member, then request deletion again.

ERASURE_BLOCKED409

The erasure request cannot proceed yet.

Why: Something has to be resolved first — usually an active subscription, or a workspace you own that other people are in. The message names it.

Fix: Resolve what the message names, then request erasure again. Nothing has been deleted in the meantime.

DUPLICATE_KEY409

A feature flag with that key already exists.

Why: Flag keys are unique within a workspace, because evaluation looks a flag up by key and two matches would make the result arbitrary.

Fix: Update the existing flag rather than creating a second one, or pick a key that is not taken.

MISSING_KEY400

No flag key was given to evaluate.

Why: The endpoint evaluates named flags and was given no names, so there is nothing for it to answer.

Fix: Add ?key=your-flag-key to the query string. Repeat the parameter to evaluate several flags in one request.

PLAYGROUND_REFUSED400

The documentation playground would not run that request.

Why: A path parameter was left empty, or a destructive call was not confirmed. The playground only runs endpoints that appear in this reference, so it can never reach one the documentation does not describe.

Fix: Read the message — it names the specific reason — and fill in what it asks for.

UNKNOWN_API_VERSION400

The Ryabi-Version header names a version we do not have.

Why: Usually a typo. An unknown version is refused rather than ignored, because a pin you believed in that silently did nothing is worse than no pin at all.

Fix: Use one of the versions listed on the changelog page, or leave the header off entirely to track the current version.

MISSING_CONTENT400

No page content was sent to link.

Why: The body had no `content` field, or it was empty. There is nothing to search for keywords in.

Fix: Send the page as `{ "content": "<p>…</p>" }`.

INVALID_FORMAT400

`format` must be auto, html or markdown.

Why: An unrecognised value. It is refused rather than guessed, because writing Markdown link syntax into HTML — or the reverse — corrupts the file rather than producing a wrong link.

Fix: Leave `format` off to detect it, or send exactly html or markdown.

INVALID_AT400

`at` is not a date we can read.

Why: `at` pins the moment scheduled rules are judged against, so a build that runs either side of a campaign boundary produces the same output. An unreadable value is refused rather than silently treated as now.

Fix: Send an ISO 8601 date-time, for example 2026-12-31T23:59:59Z.

Rate limits and quotas

RATE_LIMITED429

Too many requests in the last minute.

Why: Limits are per key, per minute. A key with its own allocation gets exactly that allocation and no more, even when the rest of the workspace is idle.

Fix: Back off and retry — exponentially, not in a tight loop. If you need more throughput for one integration, give that key a larger allocation in the dashboard.

BUDGET_EXCEEDED429

This key used its whole monthly call budget.

Why: The key has a monthly cap and has reached it. It resets at the start of the next calendar month, in UTC.

Fix: Raise the cap in the dashboard, or wait for the month to roll over. If this was unexpected, check for a retry loop — that is what the cap is there to catch.

QR_LIMIT_REACHED403

Your plan's allowance of saved QR codes is used up.

Why: This counts SAVED standalone QR codes only. A QR attached to a short link does not count — that link is already metered by the link allowance — and a one-off render from /v1/qr/render is free and counts against nothing. QR codes saved by a test-mode key do not count.

Fix: Delete a saved QR code you no longer need, or upgrade the plan.

QR_PAGE_TOO_LARGE400

You asked for QR codes on a page of more than 50 links.

Why: `include=qr` renders a real SVG per link. Fifty is already a lot of work for one request; a few hundred is a request that dies half-way and tells you nothing, so it is refused up front instead.

Fix: Lower perPage to 50 or below, or drop include=qr and fetch codes for the links you actually need.

TOO_MANY_SESSIONS429

Too many CLI listen sessions are open at once.

Why: There is a per-workspace cap, named in the message. Abandoned terminals hold their slot until the session times out.

Fix: Close a session you are no longer using, or wait for an idle one to expire on its own.

CONTENT_TOO_LARGE413

The page is bigger than this endpoint will process.

Why: Keyword matching holds the whole document in memory and scans it once per rule, so there is a ceiling on how large one page may be.

Fix: Split the page, or link the pieces separately as your build renders them. A generous article is well under the limit.

Resources

NOT_FOUND404

No such resource in this workspace.

Why: The id does not exist, or it belongs to a different workspace. Both answer identically so an id cannot be probed from outside.

Fix: Check the id, and check you are acting in the right workspace. If you are using a test-mode key, remember it cannot see production resources.

USER_NOT_FOUND404

The signed-in account has no matching record.

Why: Rare, and usually means a half-finished signup.

Fix: Sign out and back in. If it persists, contact support.

NO_WORKSPACE404

This account does not belong to any workspace yet.

Why: Signup completed but no workspace was created.

Fix: Create a workspace in the dashboard, then retry.

QR_NOT_FOUND404

No such QR code in this workspace.

Why: The id does not exist, or it belongs to a different workspace. Both answer the same way so an id cannot be probed from outside.

Fix: List your QR codes first and use an id from that response. If you are sending an X-Workspace-Id header, check it names the right workspace.

PAGE_NOT_FOUND404

No such page in this workspace.

Why: The id does not exist, or it belongs to a different workspace. Both answer the same way so an id cannot be probed from outside.

Fix: List your pages first and use an id from that response. If you are sending an X-Workspace-Id header, check it names the right workspace.

TEMPLATE_NOT_FOUND404

No such template or template pack.

Why: The id is wrong, or the template has since been deleted or unpublished by its owner.

Fix: List the available templates first and use an id from that response rather than one you stored earlier.

NOT_PUBLISHED403

The page is not published.

Why: Analytics and other public surfaces only exist once a page is live, because nothing can have visited a page that was never served.

Fix: Publish the page first. Analytics start from the moment it goes live, not from when it was created.

SESSION_GONE404

That CLI listen session is not open.

Why: It timed out, or it was closed from another terminal. Sessions expire on their own so an abandoned one cannot hold a slot forever.

Fix: Start the session again. Any events sent while it was closed are lost.

NO_SUCH_USER404

No account uses that email address.

Why: An affiliate needs an account before they can be enrolled, because there is nothing to attribute earnings to or pay out to otherwise.

Fix: Ask them to sign up first, then enrol them with the same address they signed up with.

Plan and billing

PLAN_NO_API403

API access is not included on this workspace's plan.

Why: The API is a paid feature. The check is against the workspace's plan, not your personal one.

Fix: Upgrade the workspace to Pro or higher.

PLAN_FEATURE_UNAVAILABLE403

That specific feature is not on this plan.

Why: The request was valid, but it used a field gated behind a higher plan — A/B testing and some routing options, for example.

Fix: Remove the gated field, or upgrade. The message names the feature.

ALLOCATION_EXCEEDED400

The rate limits handed out to your keys add up to more than your plan allows.

Why: Allocations are slices of one workspace-wide ceiling. The total across all keys cannot exceed it.

Fix: Lower another key's allocation first. `error.details` gives the current total and the ceiling.

Extra detail: `allocated`: current total. `ceiling`: what the plan allows.

PLAN_REQUIRED402

This feature needs a paid plan.

Why: The feature is gated behind a plan the workspace is not on. The message names which plan it needs.

Fix: Upgrade the workspace, or remove the gated option from the request if you do not need it.

PLAN_NO_WEBHOOKS403

Webhooks are not included on this workspace's plan.

Why: Webhooks are a Business feature. The check is against the workspace's plan, not your personal one.

Fix: Upgrade the workspace to Business or higher. Polling the API is a workable stand-in until then.

Domains

DOMAIN_TAKEN409

That domain is already connected somewhere.

Why: Either it is already on your workspace, or another workspace claimed it first. The message distinguishes the two, because a domain can only be connected once across the whole platform.

Fix: If it is already yours, there is nothing to do. If another workspace has it and the domain is genuinely yours, contact support with proof of ownership.

INVALID_DOMAIN400

That is not a domain name.

Why: The value has a scheme, a path, a port, or characters a hostname cannot contain. Pasting a full URL instead of a hostname is the usual cause.

Fix: Send just the hostname — example.com, not https://example.com/path. Trim any trailing slash as well.

RESERVED_DOMAIN400

That domain cannot be used to publish a page.

Why: Some hostnames are reserved for the platform itself. Publishing on one would let a page impersonate part of our own site, which is a phishing vector regardless of who asked for it.

Fix: Publish on your own connected domain, or on the shared link domain.

DOMAIN_NOT_VERIFIED403

The domain is connected but not verified yet.

Why: Verification proves you control the DNS. Until it passes we will not serve anything on the domain, because serving on an unverified name is how a domain takeover becomes a hosted phishing page.

Fix: Add the DNS records shown in the dashboard, then wait for them to propagate. That is usually minutes and occasionally an hour.

INVALID_TARGET400

The forwarding target is not a valid URL.

Why: A domain forward needs a complete, absolute URL. A bare hostname or a path on its own gives a browser nowhere to go.

Fix: Send a full URL including the scheme, for example https://example.com.

REDIRECT_LOOP400

That forward points the domain at itself.

Why: A domain forwarding to its own hostname never terminates. The browser gives up after a handful of hops and shows the visitor an error page.

Fix: Point the forward at a different hostname. A loop is invisible to a target probe, which is why it is refused here rather than discovered later.

Files and media

NO_FILE400

No file was attached.

Why: The request had no field named file. Sending a JSON body to an upload endpoint produces this too, because there is no file field to find in one.

Fix: Send the request as multipart/form-data with a field named file. Do not set Content-Type by hand — your HTTP client has to add the boundary.

NO_FILES400

No files were attached.

Why: This endpoint takes a repeated files field and received none of them.

Fix: Attach at least one entry named files. Repeat the field name once per file rather than sending an array in a single field.

TOO_MANY_FILES400

More files than this endpoint accepts.

Why: There is a per-request cap so one upload cannot exhaust the request timeout. The message names the exact limit.

Fix: Split the upload across several requests. Nothing was stored, so there is nothing to clean up before retrying.

EMPTY_FILE400

The file was zero bytes.

Why: Usually a form field that was set but never populated, or a read stream that was already consumed before the request was built.

Fix: Check the file's size on disk before uploading. If you are streaming, make sure the stream has not already been read.

FILE_TOO_LARGE413

The file is over the size limit.

Why: There is a fixed maximum per file, named in the message. It applies to the encoded upload, which is slightly larger than the file on disk.

Fix: Compress the file, or host it somewhere else and link to it instead of uploading it here.

MEDIA_TOO_LARGE413

The media file is too large to transcribe.

Why: Transcription has its own ceiling, lower than ordinary uploads, because the model provider imposes one.

Fix: Trim the recording, re-encode it at a lower bitrate, or paste a transcript directly if you already have one.

MEDIA_UNREACHABLE502

We could not download the media you pointed at.

Why: The URL timed out, refused the connection, or answered with something that was not a media file. A link requiring a login does this.

Fix: Check the URL is publicly reachable, then try again.

BLOCKED_TYPE415

That file type is not allowed.

Why: Executables, scripts and archives that can contain them are refused. A storefront that can serve an .exe is a malware distribution channel with our name on it.

Fix: Package the file as a PDF or a zip of documents, or host it yourself.

UNSUPPORTED_TYPE415

That file type is not one this endpoint handles.

Why: Different endpoints accept different types, and the message names the one it actually received — which is detected from the bytes, not from the filename.

Fix: Convert the file to a supported format, or use the endpoint that handles this type.

INVALID_IMAGE400

The image could not be decoded.

Why: The bytes are not a format we can read, or the file is truncated. Renaming a file to .png does not make it one, and that is the usual cause.

Fix: Open the file to confirm it is really an image, then re-export it as PNG, JPEG or WebP and upload again.

EDIT_FAILED400

The image edit could not be applied.

Why: Either the source file is unreadable, or the crop rectangle falls partly or wholly outside the image.

Fix: Read the image dimensions first and clamp the crop to them. Nothing was stored, so the original is untouched.

UPLOAD_FAILED500

We could not store the file.

Why: A storage failure on our side. Nothing about your request caused it, and nothing was partially written.

Fix: Retry once after a short delay. If it keeps happening, contact support with the time — that is enough for us to find it.

INVALID_BODY400

This endpoint expects multipart/form-data.

Why: A JSON body was sent to an endpoint that takes a file upload, so there was no form data to read.

Fix: Send the request as multipart/form-data. Let your HTTP client set the Content-Type so it can add the multipart boundary.

INVALID_EXPIRY400

The expiry date is not usable.

Why: It could not be parsed, or it is already in the past — which would make the thing you are creating unavailable the moment it existed.

Fix: Send an ISO 8601 date-time in the future, for example 2026-12-31T23:59:59Z.

INVALID_LIMIT400

The download limit is not a positive number.

Why: Zero and negative values would make the file unavailable the moment it was created, which is never what anyone means.

Fix: Send a whole number of one or more. Omit the field entirely if you want unlimited downloads.

Payments and products

OUT_OF_STOCK409

That product has sold out.

Why: Stock is checked when checkout opens, not when the page rendered, so a page loaded a few minutes ago can still hit this. That is deliberate — the alternative is overselling.

Fix: Restock the product in the dashboard, or remove the stock limit if it was not intended. No payment was taken.

COUPON_INVALID400

The coupon cannot be used.

Why: It does not exist, it has expired, it has been redeemed its maximum number of times, or it does not apply to the items in this order.

Fix: Check the code's conditions in the dashboard. All four causes answer the same way so a coupon code cannot be brute-forced from the response.

MIXED_CURRENCY400

The cart contains items priced in different currencies.

Why: One checkout is one currency. We will not guess an exchange rate on somebody's behalf and charge them the result.

Fix: Split the order, or price the items in a single currency.

NOT_AVAILABLE400

That slot has no price yet.

Why: A bookable slot cannot be sold until a price is set on it. A slot with no price would otherwise check out at zero.

Fix: Set a price on the slot in the dashboard, or mark it as free if that is what you meant.

NOT_ENABLED400

This booking is not priced correctly.

Why: Its pricing configuration is incomplete or inconsistent, so no checkout can be built from it without guessing an amount.

Fix: Open the booking in the dashboard and finish its pricing. The visitor was not charged.

CHECKOUT_UNAVAILABLE503

Checkout is not available right now.

Why: The payment provider is unreachable, or it is not configured for this workspace. No payment was attempted either way.

Fix: Try again shortly. If it persists, check the workspace's payment settings are complete.

CHECKOUT_FAILED502

The payment provider refused to open a checkout.

Why: The provider returned an error rather than a checkout session. The message passes on what it said, verbatim.

Fix: Read the provider's message — it is usually specific. If it is not actionable, contact support. No payment was taken.

BILLING_NOT_CONFIGURED503

This plan has no price configured yet.

Why: A plan needs a price registered with the payment provider before it can be bought. This is a gap in the deployment's configuration, not a fault in your request.

Fix: Contact support — no change to your request will help, because the missing piece is on our side.

ALREADY_SUBSCRIBED409

This workspace already has an active subscription.

Why: Opening a second checkout would create a second subscription and bill the workspace twice for the same thing.

Fix: Use the billing portal to change plans instead. Upgrades and downgrades are prorated there.

AI features

AI_UNAVAILABLE501

This AI feature is not configured on this deployment.

Why: AI features need a model provider key. Without one they ship dark rather than half-working, so the endpoint says so plainly instead of failing in a way that looks like a bug.

Fix: An operator has to configure the provider. Nothing you change in the request will help.

AI_FAILED502

The model could not complete the request.

Why: The provider returned an error or timed out. Usually transient, and usually unrelated to what you sent.

Fix: Retry once after a short delay. If it keeps failing on the same input, that input is likely the problem — try a smaller or simpler one.

BAD_SOURCE400

The source image or URL could not be used.

Why: The message names the specific problem: unreachable, the wrong type, or over the size limit.

Fix: Read the message and correct the source. If the URL needs a login, upload the file directly instead.

NO_SOURCE422

There was nothing readable on that page.

Why: The page builds its content in the browser, or it is behind a login. We read the HTML exactly as the server sends it, and that HTML was empty of anything usable.

Fix: Write the title and description by hand, or point at a page that renders its content server-side.

UNSCANNABLE422

The generated QR artwork could not be scanned.

Why: Every candidate is decoded before it is offered to you, and none of them decoded. A busy or dark style destroys the contrast a scanner needs. Offering a pretty code that does not scan would be worse than refusing.

Fix: Try a simpler prompt, a lighter style, or fewer visual elements around the code's corners.

Server

METHOD_NOT_ALLOWED405

That endpoint does not accept this HTTP method.

Why: Usually a GET where a POST was meant, or the reverse.

Fix: Check the reference for the method this endpoint expects.

GENERATION_FAILED500

The QR code could not be rendered.

Why: A rendering failure on our side, most often from an unusual combination of size, error-correction level and style.

Fix: Retry with the default options. If that succeeds, reintroduce your options one at a time to find the one that breaks it.

RENDER_FAILED500

The share card could not be rendered.

Why: An image-generation failure on our side. Nothing about your request caused it, and nothing was stored.

Fix: Retry once after a short delay. If it persists, contact support with the page id and the time.

VERIFY_FAILED500

The code could not be verified.

Why: Something failed while decoding. This is not a decision that the code is invalid — that would come back as a successful response saying so.

Fix: Retry once. A failure that persists on the same image is worth reporting with the image attached.

CREATE_FAILED500

The affiliate could not be enrolled.

Why: A failure on our side rather than a problem with your request. The enrolment was not partially created.

Fix: Retry once after a short delay. If it persists, contact support with the page id and the affiliate's email.

CODE_FAILED500

A referral code could not be assigned.

Why: A failure on our side while generating a code that is not already taken. The affiliate was not left half-enrolled.

Fix: Retry once after a short delay. If it persists, contact support with the page id.

NOT_CONFIGURED501

This feature is not enabled on this deployment.

Why: It needs a third-party credential that has not been configured. Features like this ship dark rather than half-working, so the endpoint says so rather than failing in a way that looks like a bug.

Fix: An operator has to configure the credential. Nothing you change in the request will help.

RESOLVE_FAILED502

The music resolver could not find or reach that release.

Why: Either the release is unknown to the provider, or the provider itself is unavailable. The message distinguishes the two, because only one of them is worth retrying.

Fix: Add the streaming links by hand if the release is unknown. Retry if the provider was down.