Get started
REFERENCE

Refusal codes

Every refusal carries three things: a code to branch on, a message a person can read, and a hint naming the next call that would succeed. Identical over REST and MCP, because both are the same service.

THE SHAPE
{ "code": "GATE_NOT_PASSED", "message": "Stage Launch is gated. Gate LAUNCH-1 is OPEN with 4 of 5 conditions met.", "hint": "Satisfy the conditions, then call evaluate_gate id=gt_41c."}

Domain codes

A rule the product is enforcing. The hint tells you how to satisfy it.

GATE_NOT_PASSED

A gate is standing between you and this action, and at least one of its conditions is open.

What to do: The message names the gate and counts its conditions; the hint names the unmet ones and the exact call that closes them. Satisfy them, then call evaluate_gate.

STAGE_SKIPS_GATE

The stage move jumps over a stage that requires a gate, so that gate would never be faced. ADR-008 authorizes every gated transition, not only the last one.

What to do: The message names the skipped stage. Move the product to it first (update_product stage=...), pass or waive its gate, then move on.

NO_NEXT_STEP

complete_next_step was called on a product that has no next step to complete.

What to do: Set one first with set_next_step (text, owner, optional due date), then complete it - or complete with `next` to write the following step in the same call.

BLOCKED_BY_DECISION

An open decision is holding this work. Someone has to make the call first.

What to do: The hint names the decision. Resolve it with decide_decision - which only a person can do.

AGENT_ACTION_DENIED

An agent principal attempted a protected act. Creating tenants, changing membership, registering agents, resolving decisions and every hard delete are refused to agents whatever their token grants.

What to do: No scope enables this - widening the token will not help. A person performs it, in the app or with their own credentials.

AUTONOMY_SCOPE_DENIED

The agent's owner limited what it may do in this workspace or on this product, and this action is above that limit. The same call may succeed elsewhere.

What to do: Propose it with request_approval where the limit still allows proposing, or ask the owner to lift it with clear_autonomy_policy. A wider token will not help: the limit is on the agent, not on the credential.

INSUFFICIENT_SCOPE

The token does not carry a scope this tool requires.

What to do: Mint a token with the scope named in the message. The scope reference lists what each one unlocks.

PLAN_LIMIT_REACHED

A ceiling on the current plan. The next create was refused; nothing that already exists was removed, hidden or degraded.

What to do: The message names the limit, the live count, what was counted and where the ceiling came from. Archive something you are done with - that frees the slot - or move up a plan.

PLAN_FEATURE_REQUIRED

The capability is not included in this plan at all. Distinct from a ceiling: retrying can never succeed.

What to do: The message names the cheapest plan that includes it. Everything already recorded stays exactly where it is.

CREDITS_EXHAUSTED

Not enough agent credits for a metered call. Credits pay for the one operation that runs a model - semantic search.

What to do: Every unmetered call still works: the board, decisions, documents, logs and context are free. Top up, or use keyword search instead of semantic.

POOL_EXHAUSTED

Every value in the pool is held. Nothing was taken and nothing already held was disturbed.

What to do: The message names the capacity, how many are held and how many you asked for. Release something you are done with (release_resource), or widen the pool - which is a human act, done in Settings.

VALUE_NOT_IN_POOL

The value is not one this pool contains - out of its ranges, or not a well-formed value of its kind. Also raised when releasing something that is not currently held.

What to do: Read the pool with get_resource_pool: it lists what the pool actually contains.

VALUE_ALREADY_HELD

Somebody holds the value you asked for. It is never silently substituted - a port you did not choose is worse than a refusal you can act on.

What to do: The message names the holder when the record knows it. Ask for a different value, allocate without `preferred` to get the lowest free one, or take it up with whoever holds it.

POOL_KIND_UNKNOWN

The pool declares a kind this build does not register, so its values cannot be interpreted. Nothing about the pool was changed.

What to do: This is not something a caller can fix by retrying or by sending different arguments. The message names the kinds this server does register.

SITE_UNKNOWN_SECTION

The site has no such section. The sections are a registry in this build, not free text.

What to do: The message names every section this server has. Pick one of them.

SITE_UNKNOWN_ENTITY_TYPE

That kind of thing cannot be placed on a site at all.

What to do: The message names the kinds that can. Nothing was changed.

SITE_SECTION_REJECTS_TYPE

The kind and the section do not go together - a release in `legal`, a product in `blog`. Refused rather than stored, because a site reads a section expecting a shape and one row of the wrong kind renders as a fault or an invented page.

What to do: The hint names the sections this kind of thing CAN go in, or what the section admits when it admits nothing of this kind.

SITE_CROSS_WORKSPACE

The thing being placed lives in a different workspace from the site. A placement is workspace-scoped and would have to belong to two.

What to do: Place something from the site's own workspace, or move the record there first. This is a boundary, not a permission - widening it is a design decision rather than a setting.

SITE_NAME_AMBIGUOUS

More than one product you can see is called that, so naming the site by name does not identify one. Nothing was changed.

What to do: The hint lists the ids of every match. Pass one of them instead of the name.

INVENTORY_KIND_UNKNOWN

The inventory records a fixed set of kinds of holding, and that is not one of them. The set is a registry in this build, not free text.

What to do: The message names every kind this server registers. A kind arrives by being added to the build, so this is not something a retry or different arguments can fix.

INVENTORY_VALUE_MALFORMED

The value is not a well-formed one of its kind, so it was refused rather than stored in a spelling nothing else would match.

What to do: The hint says what a value of this kind looks like. A domain is a hostname - no path, no credentials, and not an IP address.

INVENTORY_ALREADY_HELD

You already hold that value. One live holding per value is a database constraint, not a check that can be raced past.

What to do: The message names the existing holding. Update it instead of adding a second, or release the old one first if this is a re-acquisition.

INVENTORY_RELEASED

The holding has been released - you do not own it any more - so it cannot be assigned, re-costed or given a new renewal date. The record stays readable on purpose.

What to do: The message says when it was released and why. If you have it again, record it as a new holding; the old row remains as the history of having had it.

IDEA_ALREADY_CONVERTED

That idea has already become a product. Converting again would create a second product from one idea and leave two records claiming the same origin.

What to do: The message names the product it became. Work on that product, or capture a new idea if this is genuinely a different one.

IDEA_CLOSED

That idea is archived or has already been converted, so it has no triage state to change. Its state is a stamp now, not something to move.

What to do: Restore it first (restore_idea) if it is archived and you want it back on the funnel; a converted idea is worked on as the product or initiative it became.

IDEA_FOR_PRODUCT

That idea is FOR an existing product - a feature of it - so it does not become a product of its own. Converting it that way would spend a product slot on what belongs inside one.

What to do: Use convert_idea_to_initiative, which creates an initiative on that product from it. If it really is a separate product, detach it first (update_idea with productId null).

IDEA_NOT_FOR_PRODUCT

That idea is free-standing - it names no product it belongs to - so there is nothing to create an initiative ON.

What to do: Use convert_idea_to_product to make a product from it, or attach it to a product first (update_idea with productId) if it is a feature of one.

REVIEW_COMPLETED

That portfolio review has been completed, so it takes no more entries. A completed review is a record of one sitting; it is not reopened.

What to do: Call start_portfolio_review - it opens the next review (or returns the one already in progress) - and record the entry there.

REVIEW_ACTION_MISMATCH

The action does not fit the subject: park/drop/convert are what a review says about an IDEA, focus/unfocus/maintain/develop/pause/resume/close about a PRODUCT. keep fits both.

What to do: The hint lists the actions that fit this subject type. Nothing was recorded and nothing was changed.

SPEND_CAP_EXCEEDED

This write would take the window's spend past the cap set for it.

What to do: Wait for the window to roll over, or raise the cap. Reads are unaffected.

BILLING_NOT_CONFIGURED

This installation is not connected to a payment provider, so nothing can be bought on it.

What to do: Nothing is wrong with the organization - every plan limit and every record still behaves exactly as before. Only a purchase is refused.

PLAN_NOT_PURCHASABLE

That plan cannot be checked out: either it is the free plan, which is what you are on when you have bought nothing, or nobody has mapped it to a price here.

What to do: get_subscription returns the plan catalog. Another plan may still be purchasable.

SUBSCRIPTION_EXISTS

The organization already holds a subscription that entitles it. One organization holds one - a second would bill twice and only one of them would be recorded.

What to do: Changing the plan or the seat count is not available yet - the payment provider's own portal cannot do it either. End the current subscription from the billing page and buy the one you want. A canceled or paused subscription does not refuse a new purchase.

NO_SUBSCRIPTION

There is no subscription to act on. The organization is on the free plan, or it holds a record the payment provider knows nothing about.

What to do: Nothing is wrong. Start a subscription from the billing page if you want a paid plan.

SUBSCRIPTION_ENDED

The subscription is already over. A finished subscription can never be reinstated by the provider.

What to do: Buy again from the billing page - the record is kept, so the purchase lands on the same customer rather than a new one. Nothing was deleted when it ended.

PROVIDER_UNAVAILABLE

The payment provider did not answer, or refused to open the checkout.

What to do: Nothing was bought and nothing about the organization changed. Try again in a moment; the provider stated a reason and it is in the server log.

PRODUCT_READ_ONLY

This product is outside the organization's product limit, so it accepts no writes. It is not deleted, not hidden and not degraded: every read works, the whole history is there, and export works exactly as before.

What to do: An owner or admin chooses which products keep writing, on the reconciliation screen the refusal names - picking this one releases it and restricts another. Moving up a plan releases every one of them at once. The message says how long the extras stay before they are archived, and archiving is recoverable too.

PROMO_INVALID

The code does not open anything. It is unknown, or it was withdrawn by whoever made it - deliberately the same answer for both, so the refusal cannot be used to find out which codes are real.

What to do: Check it against what you were sent: the shape is MM-XXXXX-XXXXX-XXXXX-XXXXX, case does not matter, and I, L and O are read as 1, 1 and 0. Your plan is untouched either way.

PROMO_EXPIRED

The code was real and its validity has run out.

What to do: Nothing about the organization changed. Ask whoever sent it whether there is a current one.

PROMO_EXHAUSTED

The code has already been redeemed as many times as it was made for.

What to do: It was a real code and it has run out - this is not something you did wrong and not a limit you hit. The plan is exactly what it was.

PROMO_ALREADY_REDEEMED

This organization has already used this code. A code counts once per organization, not once per person, so a colleague may have used it.

What to do: The plan it granted is already in force, or has since run its course - the billing page shows what is active and until when. Entering it again would not add time.

ENTITY_CHANGED

Somebody else changed this record after you read it, so your write was refused rather than applied on top. Nothing was overwritten and nothing was lost.

What to do: Re-read the record and re-apply your change. The refusal names WHICH fields moved (never their values) and when, so a caller writing different fields can simply retry. Omitting `expectedVersion` skips this check entirely - which is the right choice for a one-shot write and the wrong one for an editor.

IDEMPOTENCY_KEY_REUSED

This idempotency key was already used for a DIFFERENT request. The first request stands; the second was refused rather than answered with the first one’s result.

What to do: Send the changed request under a NEW key. Reusing a key is how a retry stays safe, so it only works for the request it was minted for - a key that answered a request about one product cannot answer one about another.

IDEMPOTENCY_IN_PROGRESS

The first call carrying this key is still running. Nothing was duplicated and nothing was lost.

What to do: Wait a moment and send the SAME key again - never a new one. The repeat returns the original result once the first call finishes, which is the whole point of the key.

CURSOR_INVALID

The `since` value is not a cursor this server issued - it is malformed or was edited.

What to do: Pass back the cursor from a previous response exactly as you received it. To start over, omit `since`: the feed is then read from the beginning, page by page.

CURSOR_SCOPE_MISMATCH

The cursor was issued for different filters, a different workspace or a different caller. A position is only meaningful for the set it was taken in, so using it here could skip changes.

What to do: Repeat the call with the same workspace and filters the cursor came from. To follow a different selection, omit `since` and keep the new checkpoint.

CURSOR_UNSUPPORTED

The cursor is in a format this server no longer accepts - it was issued before its compatibility window ended.

What to do: Omit `since` to read the feed again from the beginning, deduplicating by id, and store the checkpoint the last page returns.

EXECUTION_PAUSED

The owner paused or archived this scheduled run. Nothing it sent was recorded.

What to do: Stop the runner. Retrying will not help - the owner resumes the run with update_execution_connection when they want it back.

REVIEW_CHECKPOINT_MOVED

Another run already moved this review past the checkpoint you started from, so your result would cover a range that is no longer next.

What to do: Read the connection again with get_execution_connection and start the review from its current feedCheckpoint. A retry of a result that was already recorded is answered with that result, not with this refusal.

SIGNUP_CLOSED

This installation is not accepting new accounts at all right now. It is a switch somebody set, not a judgement about you.

What to do: Nothing about your details is wrong and no code would help. An account that already exists still signs in normally. Ask whoever runs the installation when it reopens.

INVITE_REQUIRED

Signup here is invite-only, and the request carried no code.

What to do: Ask somebody who already has an account - every account comes with invites to give away. The code is a bearer code, so they can simply pass it to you.

INVITE_INVALID

That code does not open the door. It is unknown, or it was withdrawn by the person who made it - deliberately the same answer for both, so the refusal cannot be used to find out which codes are real.

What to do: Check it against what you were sent: the shape is MM-XXXXX-XXXXX-XXXXX-XXXXX, case does not matter, and the letters I, L and O are read as 1, 1 and 0. If it still fails, ask for a new one.

INVITE_EXPIRED

The code was real and its validity has run out.

What to do: Ask whoever sent it for a fresh one - generating another takes them a moment, and this is not a limit they have hit.

INVITE_EXHAUSTED

The code has already brought in as many accounts as it was made for.

What to do: Ask for a new code. Nothing about the account it created is affected, and the person who sent it has not lost anything else.

INVITE_QUOTA_REACHED

You are holding as many invites as your quota allows. The refusal names the ceiling, how many you are using and exactly what was counted.

What to do: Revoke an invite nobody has used and the slot comes back immediately. An invite that has already brought somebody in is spent and stays spent - revoking it would not un-create their account.

PASSWORD_NOT_SET

This account has no password at all, so the password form has nothing to check. It was opened through an external account - the refusal names which one - and that is how it is opened.

What to do: Use the sign-in button for the provider named in the message. Once you are in, Settings lets you add a password as a second way in if you want one. Note that this refusal, unlike every other answer the sign-in form gives, confirms an account with that address exists - it is answered honestly on purpose, because there is no password reset to fall back on.

OAUTH_NOT_CONFIGURED

This installation cannot complete a sign-in with that provider - either it does not know the provider at all, or its credentials were never filled in.

What to do: Use one of the buttons the sign-in screen actually offers, or a password. Nothing you can do at the provider changes this; it is configuration on this server.

OAUTH_STATE_INVALID

The sign-in that came back could not be shown to be the one that started in this browser. Every way that can happen - the attempt sat too long, the tab was reopened from history, the cookie was cleared, the link was opened somewhere else - gives this same answer, on purpose: telling one apart from another only helps somebody probing.

What to do: Start the sign-in again from the beginning. Nothing was created and nothing was linked.

OAUTH_EXCHANGE_FAILED

The provider was reached and did not give us an account to work with - it declined the exchange, or it did not answer in time.

What to do: Try again in a moment. If it keeps happening, sign in with a password if you have one; the provider being unavailable does not affect anything about your account.

OAUTH_EMAIL_UNVERIFIED

The provider would not vouch for the email address on that account, so it cannot be used to reach a makermap account. Accepting it would let anybody claim an address by typing it into a new provider account.

What to do: Verify the address at the provider and try again - on GitHub, also make sure you granted access to your email addresses. If you already have a makermap account, sign in the usual way first and connect the provider from Settings, which proves the account is yours.

OAUTH_ALREADY_LINKED

That provider account already opens a different makermap account.

What to do: Sign in with it instead of connecting it, or disconnect it from the other account first. It is never moved automatically - doing that would take a way in away from an account that has it, possibly its only one.

OAUTH_ACCOUNT_CLOSED

The makermap account that provider account belongs to has been closed.

What to do: Nothing here reopens it. Ask whoever runs the installation.

USER_IDENTITY_NOT_FOUND

No connected account of yours has that id.

What to do: Re-read the list and use an id from it. An identity belonging to somebody else answers the same way, deliberately - the alternative is a way of testing whether an id exists.

LAST_SIGN_IN_METHOD

Disconnecting that provider would leave the account with no way to sign in at all - it is the only one, and the account has no password.

What to do: Connect a second provider first, then disconnect this one. The refusal is not a formality: there is no password reset in this product, so an account with no key left is unreachable rather than inconvenient.

OAUTH_FAILED

A sign-in through a provider failed for a reason this build could not name. It is the honest answer of a fallback, not a description.

What to do: Try again. If it repeats, the server log holds the real cause - this code exists so a failure still lands you on a page rather than on a blank error.

PLATFORM_SETTING_UNKNOWN

A platform setting was addressed by a name this build does not have in its registry.

What to do: The hint lists the settings that exist. A name that is not one of them is refused rather than stored, because a value nothing reads is indistinguishable from a feature that does not work.

PLATFORM_SETTING_INVALID

The setting exists and the value is not one it accepts.

What to do: The hint states what the setting accepts. Nothing was written.

GRANT_REASON_REQUIRED

A plan was granted with no explanation. The field is required and whitespace does not count as one.

What to do: Say what the grant is for - a design partner, a launch compensation, the founder's own installation. A year from now the row has to explain itself without anybody remembering.

PLAN_NOT_GRANTABLE

The grant names a plan that is not in this build's registry.

What to do: The hint lists the plans that exist. Nothing was written.

VALIDATION_ERROR

The arguments are well formed but the values are not usable - out of range, mutually exclusive, or referring to something that cannot be used that way.

What to do: The message names the field and the constraint. This is not a schema error; the shape was accepted.

WORKSPACE_ORG_MISMATCH

The workspace and the organization named in the same call belong to different tenants.

What to do: Pass the workspace alone and let the organization be derived from it.

SELF_DEPENDENCY

Something was asked to depend on itself.

What to do: Check the two ids in the call - they are the same.

DEPENDENCY_CYCLE

The edge would close a cycle in the temporal graph, so no ordering could satisfy it.

What to do: The message names the path. Remove one edge along it first.

INVALID_TARGET

The target exists but is not a thing this operation can be applied to.

What to do: The message says what kind of target is expected.

ALREADY_FORMALIZED

A captured block has already been turned into a task, epic, milestone or initiative.

What to do: Open what it became - the message names it. A capture formalizes once.

PRODUCT_NOT_SOFT_DELETED

restore_product was called on a product that was never deleted.

What to do: Nothing to restore. If it is missing from a list, it is probably archived rather than deleted.

NO_COLUMNS

The board has no columns, so there is nowhere to put a task.

What to do: Create a column with create_board_column first.

DESIGN_SPEC_INVALID

A design spec did not satisfy the DSL.

What to do: The message carries the offending path. Fix that node rather than resending the whole document.

DESIGN_VARIANT_NOT_CHOSEN

A design review cannot close because no variant has been chosen.

What to do: Choose one, then close. Which variant ships is a decision, not a default.

UNKNOWN_BLOCK_TYPE

A design spec used a block type the DSL does not define.

What to do: The message lists the block types that exist.

TOKEN_KEY_UNKNOWN

A design spec referenced a design token that does not exist.

What to do: The message lists the valid keys.

TOKEN_VALUE_INVALID

A design token was given a value outside its allowed set.

What to do: The message names the constraint.

Transport codes

Derived from the HTTP status when no domain code was attached. Getting one of these where a domain rule was clearly being enforced means the code is missing - a bug worth reporting.

INVALID_REQUEST

The request was rejected and no domain code was attached - usually a malformed body or a failed schema.

What to do: Check the arguments against the tool schema. If the message describes a domain rule rather than a shape, the missing code is a bug worth reporting.

UNAUTHENTICATED

No usable credential. The token is missing, malformed, revoked or expired.

What to do: Check the Authorization header carries the whole token, then mint a new one.

FORBIDDEN

Authenticated, but not permitted to reach this tenant or record.

What to do: Check which workspace the record lives in and which the token can reach.

NOT_FOUND

No such record - or none the caller is allowed to see. The two are deliberately indistinguishable.

What to do: Confirm the id and the workspace. A record in another workspace answers exactly like one that does not exist.

CONFLICT

The write collides with the current state - typically a name or slug already in use.

What to do: Re-read and retry with a different value. The message says what collided. A record that MOVED under you is the separate, named ENTITY_CHANGED.

ERROR

An unexpected failure with no status to derive from. This is the only code that means "we do not know".

What to do: Retry once. If it repeats, it is a bug.

If you are reading a refusal and still do not know what to do, that is a defect in the refusal and worth reporting as one. See also troubleshooting.