The recipe a coding agent (Claude Code, Cursor, Codex, OpenCode) follows to
work on a Norbix backend from the shell. norbix ai init writes a short
version of this into your project (a Claude Code skill, a CLAUDE.md /
AGENTS.md block, a Cursor rule). The guarantees behind it are in
agent-contract.md.
npm install -g @norbix.ai/cli # or: npx @norbix.ai/cli …
norbix --versionFirst call of a session: norbix whoami --json. "auth": "none" there
(or exit code 4 from any other command) means no usable
credentials. The agent starts a browser sign-in and the human approves it in
the dashboard. The agent never runs norbix configure or a plain
norbix login (they need a terminal), and never asks for a password, key or
token in the chat.
-
On a developer's machine: the agent signs in itself, in two steps (its shell commands time out; the code lives 10 minutes):
norbix login --no-browser --json→{status: "pending", userCode, verificationUriComplete, expiresIn, next}. Show the user the link and the code. They open it, check the code, pick the roles the agent gets and press Allow. Never open the link yourself.norbix login --wait --json— waits up to 90 s. Exit 0: signed in as the AI service userNorbix CLI (<computer>)(removable under Account → AI service users;norbix whoami --jsonshows it undersession). Exit 4AUTHORIZATION_PENDING: not approved yet, run it again.ACCESS_DENIED/EXPIRED_TOKEN: start over with step 1 (ACCESS_DENIEDwithcontext.reasonEmailNotVerified: ask the person to verify their email in the dashboard first).
Not on norbix.ai? Add
--host <their dashboard or Hub address>(or--profile <name>) to both steps. The user can also sign in themselves withnorbix loginin their own terminal. -
In CI: never a browser sign-in. Environment variables (
NORBIX_HOST— omit for norbix.ai —,NORBIX_API_KEY,NORBIX_PROJECT_ID, andNORBIX_REGION,NORBIX_ENV,NORBIX_ACCOUNT_IDwhen needed), or a profile created once by a human (norbix login --api-key nbsu_… --project <id> --host <host> --profile ci, then every call takes--profile ci).
When a sign-in has ended (removed in the dashboard, or 30 days unused), a
command exits 4 with error.code SESSION_EXPIRED: sign in again as above.
Never print, echo or paste a key or token, and never read ~/.norbix.
A project always has PROD and may have more (TEST, STAGING, …). Check first:
norbix whoami --json (environments, envRule) or norbix env list --json.
- Only PROD: pass no env — every command works in PROD (
--env PROD/--prodchanges nothing). - More than one: every project command needs the env:
--env <name>(--prodis short for--env PROD) orNORBIX_ENV. Ask the user which one when the task does not say it — never assume PROD. Without it the command exits 2 witherror.codeENV_REQUIREDand the list of environments, and sends nothing (a--dry-runfails the same way). - A default saved with
norbix env use <name>(orenv =in a profile) also counts; each command then printsenv: <name> (<where it is saved>)on stderr. Read that line. - Account commands (no project) and
norbix env …are not checked.
The list of environments (and the promotion ladder, promotionOrder) is
cached for 5 minutes per project; env list / use / delete and an
"environment does not exist" error refresh it.
Never build PROD by hand when the project has a lower env. Schemas, taxonomies, terms, templates, footers, signatures and triggers are made in TEST and promoted:
norbix env promote --from TEST --to PROD --json— a preview (the default), nothing changes. Show the usersummary,items(deleted ones withrecordCount),integrations(action,warning,note,realSender) andnotPromoted.- After the user agreed: the same command with
--yes(--seed / --skip / --map id=targetIdonly for integrations the user decided;--confirm-record-lossonly whenrequiresConfirmationis true and the user accepts it — exit 3CM-ERRORS-ENVIRONMENTS-013means it was missing). - Keep
fromVersion:norbix env rollback --to PROD --version <fromVersion>(preview, then--yes) undoes it.promoted: false(withsummary.isNoOp) means nothing needed promoting: nothing was written and there is nofromVersion— tell the user "Nothing to promote — PROD already matches TEST.".
A content command run straight in PROD while TEST exists prints
Note: PROD is promoted from TEST — … on stderr: make the change in TEST and
promote instead.
Stdout is then exactly one JSON document: the result, or
{"error": {code, message, status, exit, fieldErrors, url, traceId, hint, docs}}.
Nothing else is printed on stdout; stderr carries progress and warnings.
norbix schema --json # every command: args, flags, examples, destructive, supportsDryRun
norbix schema users delete --json # one command
norbix users delete --help # the human page, with a realistic example
norbix hub scheduler --json # SDK methods of a module with their request fields
norbix hub scheduler task delete --helpPrefer the named commands (db, users, files, email, sms, push,
scheduler, logs, env, apikeys, webhooks, payments, account).
When there is none, every SDK endpoint is reachable with plain words:
norbix hub scheduler task delete 66b2f0a1c3d4e5f6a7b8c9d0 --yes --json
norbix hub scheduler task save --body '{"name":"nightly","cron":"0 2 * * *","initiatorUserId":"usr_123","isEnabled":true,"stopOnError":false,"task":{"type":"EmailCampaign","campaign":{"source":"AllUsers","templateId":"tpl_123"}}}' --json--body takes the whole request as JSON (nothing is merged with
--field flags); --field:str value forces a string when a value looks
like a number.
Before writing a data schema, print the rules and follow them:
norbix db schema rules # which field for which value, with the exact JSON shape
norbix db schema rules --json # the same as data- Email →
{"type":"string","format":"email"}; link / website / app link →{"type":"string","format":"uri"}— never bare text. - Language / locale, currency code, country, status, category — any list
people pick from → a taxonomy picker
(
{"source":"taxonomy","taxonomyId":"<id>"}), never bare text and not an inline enum. An enum is only for a developer-facing flag the app code branches on. - A person (assignee, author,
*_user_id) → a user reference ({"source":"user","displayField":"displayName"}), never a text id. Never addcreatedBy/owner/createdAt: the platform keeps them in_meta(settings.hasRecordOwner).
The Hub checks these rules when a schema is saved (db schema create,
hub database schema save) and when hub account schema validate runs. A
schema that breaks one is still saved; the answer carries warnings (on
stderr as text, and in the JSON on stdout). Fix each named field and save
again.
norbix db update orders --id 66b2f0a1c3d4e5f6a7b8c9d0 --update '{"status":"shipped"}' --dry-run --json
# inspect "http" and "request" …
norbix db update orders --id 66b2f0a1c3d4e5f6a7b8c9d0 --update '{"status":"shipped"}' --jsonShow the dry run to the user before the real call. A destructive
command (destructive: true in the schema) additionally needs --yes; without it, and without a terminal, it exits 3 and sends
nothing. Never add --yes to a first attempt — dry-run first.
| exit | meaning | next step |
|---|---|---|
| 0 | ok | |
| 2 | usage error | read error.message, fix the call (--help) |
| 3 | confirmation required | re-run with --yes after a dry run |
| 4 | not authenticated / forbidden | ask for credentials or another profile |
| 5 | not found | check the id, --env, --project; list first |
| 6 | validation | error.fieldErrors names the fields |
| 7 | network | check --region, endpoints, connectivity |
| 8 | server / rate limit | retry with backoff; keep error.traceId |
| 9 | cancelled | |
| 1 | internal | report with error.traceId |
error.hint says what to do; error.docs says where to read.
cd my-project
norbix ai init # Claude Code skill + CLAUDE.md block
norbix ai init --target all # also AGENTS.md and a Cursor rule
norbix ai init --dry-run # see what it would writeFiles are never overwritten without --force; an existing block is left
alone.