Reference

Error codes

Every AccessError code with its meaning and fix, every case of ACCESS_OPTIONS_INVALID, and the codes Access's routes answer with.

Read as Markdown

createAccess, its instance, aventaraAccess(), accessOf(auth), the registry, and an engine checking its lock or a question throw AccessError, exported from @aventara/access. Its code says which refusal it is. Its message starts Aventara Access: and says what to change. The registry answers with Better Auth's { code, message } body.

Code
import { AccessError } from "@aventara/access";

try {
	// createAccess(...), access.policy, access.registry.rotateKey(...)
} catch (error) {
	if (error instanceof AccessError && error.code === "ACCESS_LOCK_MISMATCH") {
		// pull again
	}
}

AccessError codes

CodeMeaningFix
ACCESS_BETTER_AUTH_VERSIONbetter-auth is missing, or outside ~1.7.12 (1.7.12 or a later 1.7 patch). Under mcp or audience, @better-auth/oauth-provider or @better-auth/cimd outside it.npm i better-auth@~1.7.12. Under mcp or audience, also npm i @better-auth/oauth-provider@~1.7.12 @better-auth/cimd@~1.7.12.
ACCESS_CLAIMS_UNDECLAREDThe rules or the guardrails read a claim, or a token carries one, that policies.claims does not declareDeclare the claim in policies.claims
ACCESS_PLUGIN_DUPLICATEbetterAuth.plugins holds jwt() or aventaraAccess(), which createAccess installs itself. Or a second Access plugin beside aventaraAccess(). Or one aventaraAccess() in two Better Auth instances whose options differ. Or a plugin that defines jwt()'s endpoints, Access's /me, (kiosk profile) anything under /kiosk/, or (registry on) a registry route.Remove it from the plugins
ACCESS_OPTIONS_INVALIDAn option Access does not take or cannot accept. Every case is below.Follow the message
ACCESS_RESOURCE_MISSINGA table resources opens is missing: Better Auth lacks its plugin, or the rules' schema lacks its Resource. Or the organization bridge without organization().Add the plugin, or run npx auth@~1.7.12 generate --yes and regenerate the model
ACCESS_LOCK_MISMATCHA pulled file no longer hashes to its pin in avAccess.lock.json, the engine was given files the lock does not pin, or requireLock is set and no lock was givenRun avaccess pull again. Never edit a pulled file. Pass the lock.
ACCESS_KEY_REFUSEDA registry key that is wrong, revoked, rotated out or another namespace's (401), or a pull key that pushes (403)Use the namespace's current key of the right kind
ACCESS_OWNERSHIPA pushed file names a Resource another namespace owns, or one the operator has not assigned it (ownership: "assigned"), or the org's guardrails grant (403)Grant only on your own Resources. Ask the owner or the operator, or add a guardrail.
ACCESS_POLICIES_INVALIDA pushed file is not a file the engine would run from (400), or is over 1 MiB (413)Push the .contract() of your rules, or the pulled file unchanged
ACCESS_NAMESPACEA namespace name that is not one, one the registry does not have, or one it has alreadyCheck the name. Use keys rotate for an existing namespace.
ACCESS_REVISION_CHANGEDA stored revision no longer hashes to its key: it was changed outside the registry (500)Restore the registry's tables, and push the file again
ACCESS_FOREIGN_CROSS_DATABASEThe rules read another namespace's Resources as foreign, and this service is on another database: the owner's beacon as its connection reads it is missing or not the one the owner pushed, or the owner pushed none (403)Run the service on the owner's database, or carry the fact in a JWT claim (policies.claims)
ACCESS_BEACON_MISMATCHAt startup, an owner's row in _aventara_beacon is missing or does not hash to this environment's pin, the pins are another environment's, a check failed before, or checkBeacons was not awaited. At push, a beacon the namespace replaced comes back (409), or the namespace's row is one the registry does not hold.Point the service at the owner's database. Pull this environment's pins. Await access.policy.checkBeacons(beacon, beaconPins, environment) before serving. Have an operator verify or delete a row the push did not write.
ACCESS_QUESTION_INVALIDA fieldsOf question without access: "read" or access: "write", or with an action that does not fit it; or a hosted call whose held is not a list of Resource namesAsk { access: "read" }, or { access: "write", action? } with create or update; call hosted({ held: ["User", ...] })

ACCESS_OPTIONS_INVALID

One code, many causes. The message names the option and the reason. The cases, by area:

AreaRefused
OptionsAn option or block createAccess does not take, or cannot read. accessOf given an instance without aventaraAccess().
Base URL and cookiesNo base URL to fix the tokens' issuer. A session cookie with sameSite: "none" and no trustedOrigins.
Claims and principalA claim read from a user field a user can set (no input: false). A principal object shared by two users.
User idsBetter Auth's numeric ids (advanced.database.generateId: "serial"), in every app and in avaccess serve and avaccess keys (User ids).
Passwordspasswords set beside your own emailAndPassword.password. Node.js 22 without @node-rs/argon2.
Better Auth tablesA grant on a table resources leaves closed. Better Auth off its Prisma adapter without resources.authTables. An opened table whose secret column is not found.
OrganizationsAn organization plugin without requireEmailVerificationOnInvitation: true under resources.members or the bridge. Under the bridge: one that lets any user create an organization, or that maps a role dynamic access control lets admins name.
MCP and audienceNo OAuth provider, or two. One listed before aventaraAccess(). disableJwtPlugin. A resource missing from its resources. No aventara:read among its scopes. A refreshTokenReuseInterval other than 0. A validateRedirectUri, a requestUriResolver or a postLogin.consentReferenceId. allowUnauthenticatedClientRegistration without mcp.openRegistration (never under audience alone). A resource that is the provider's UserInfo URL. The service profile. Under mcp, an http issuer off 127.0.0.1 and [::1]. Under audience, enforcePerClientResources: false, and, beside mcp, the audience in clientRegistrationDefaultResources or clientRegistrationAllowedResources.
ProfilesA profile of two kinds, or the kiosk's keys on another kind.
KioskBetter Auth's cookie cache. secondaryStorage with sessions kept out of the database. bearer() or multiSession(). crossSubDomainCookies or SameSite=None. A plain http: base URL off a loopback host and off insecureLan. A dynamic baseURL that serves a non-loopback host over http:.
insecureLanA range that is not a canonical private IPv4 CIDR of /16 or narrower. An empty or repeated list. A base URL that is a hostname, IPv6, outside every range, or dynamic. A cookie setting that makes a cookie Secure.

Kiosk and password codes

These are Better Auth APIError codes from Access's routes.

CodeStatusWhen
KIOSK_DEVICE_NOT_ENROLLED401The request brings no current device credential
INVALID_USER_OR_PIN401A wrong PIN, a user with none, or no such user (400 for a PIN that is not 6 to 12 digits)
KIOSK_PIN_LOCKED429The user's backoff on this device, or the device's limit. X-Retry-After says when.
KIOSK_PIN_DISABLED403100 wrong PINs in a row for this user on this device
KIOSK_DEVICE_BUSY409Two switches on one device at once, or a request or sign-in with the credential the last switch replaced within 30 seconds
KIOSK_FULL_CREDENTIAL_REQUIRED, SESSION_NOT_FRESH403Enrolling, listing, revoking or setting a PIN from a PIN session, or a session older than freshAge
KIOSK_ENROLL_REFUSED403canEnrollDevice did not answer true
KIOSK_SESSION_NOT_ALLOWED403A PIN session on a route it does not reach, a bound session asking for a token or managing OAuth clients or consents, or a sign-in on a till that would resume an authorization request
KIOSK_DEVICE_NOT_FOUND404Revoking a device that is not enrolled
USER_NOT_FOUND404kioskSetUserPin for no such user
PIN_TOO_WEAK400A PIN that is not 6 to 12 digits, or one a guesser tries first
PASSWORD_TOO_LONG400A password longer than Better Auth's emailAndPassword.maxPasswordLength (128 by default)

Framework codes you will see

Access does not define these, but it causes them.

CodeStatusWhy
A4002403The policy refused the request: no grant allowed it, a deny matched, or it selected a field governed for reading, or set one governed for writing, that no grant opened. A read of one of Access's own tables ends here too. See Error codes in the framework docs.
A3000500A rule compares a datetime, decimal or bigint field in a scope or an exists question. The request is refused. See Rules.

Better Auth's own origin codes also appear on sign-in: MISSING_OR_NULL_ORIGIN and INVALID_ORIGIN (both 403). Send an Origin header that matches Better Auth's base URL or one of its trustedOrigins.

Warnings

Node.js warnings (process.emitWarning, type AventaraAccessWarning), printed when Aventara hosts the engine (@aventara/core 1.0.0-rc.10 or later), each once per process and namespace. They change no decision. Node's --disable-warning=<code> silences one.

CodeWhyFix
ACCESS_READ_GOVERNED_UNIQUEA field governed as "read" is unique (one of its Resource's identifiers): a write that sends a value another row holds fails on the duplicate, which tells the writer the value exists. Warned when the rules compile and when the engine is built.Govern it as "both", or accept that writers learn which values exist.
ACCESS_REFERENCE_UNREADABLEA role may create or update a reference to a Resource the app serves that no read grant reaches for it. Aventara refuses each such write (A4002). See Rules.Grant read on the target to that role, or leave the reference out of the write (write: { except }). For Better Auth's User, open it first with resources: { users: true } in createAccess; the warning names that key
ACCESS_FOREIGN_REFERENCE_UNCHECKEDA role's create or update grant may write a reference to a foreign Resource with no condition through that relation, so any row of it can be linked by its identifier. See Rules.Constrain the link through the relation (a where on a create grant, an after on an update grant), or leave the reference out of the write

Audit reasons

A decision event's reason names the deciding rule (The audit log).

ReasonMeaning
grant:<id>A grant allowed it
deny:<id>A deny matched
deny:guardrail:<id>A guardrail matched
better-auth:<Resource>A built-in deny closed one of Better Auth's tables
better-auth:<Resource>/<actions>A write an opened table never takes
deny:mcp-scopeAn agent's token lacks the scope for the question
deny:mcp-audienceAn agent's token used under an origin other than agent
no grantNothing allowed it