CLI reference
Every command of palbase 0.18.6 — the CLI in the palbase npm package — as its own help prints it. Run it with npx palbase <command>, or palbase <command> once the package is installed; palbase help <command> prints the same in a terminal. Signing in, choosing an environment and using a token in CI are in the CLI overview.
382 commands in 32 groups.
Global flags
Every command takes these.
-P, --project <project> the project (name or id; org/project when the name is in two organizations)
-E, --env <env> the project's environment (slug or ref)
--json print the answer as JSON
--non-interactive never ask: refuse what would need a question
-y, --yes confirm a change to a production environment without asking
--debug print each request the command sends
-h, --help show help
General
palbase login
Sign in with your Studio account
palbase login
Shows a one-time code and opens Studio, where you approve it while signed in there. The CLI then holds a session of yours: Studio lists it with your others, and ending it there signs the CLI out. On a machine with no browser (a server, an agent), open the address it prints on any device.
--no-browser print the address instead of opening a browser
palbase logout
Sign out: the session is ended at Palbase and forgotten on this computer
palbase logout
palbase whoami
The account the CLI acts as, and the environment chosen for this directory
palbase whoami
palbase use
Choose the environment this directory acts on (kept on this computer)
palbase use [<project>[/<env>] | <org>/<project>/<env>] [--clear]
Commands run in this directory, or a directory under it, then act on that environment unless -P/-E say otherwise. Nothing is written into the project: two checkouts of it can each act on their own environment. With no argument and a person at the terminal, it lists your environments to pick from.
--clear forget the choice made for this directory
palbase status
The environment this directory acts on: who you are there, its machines and databases
palbase status
palbase open
Open the environment in Studio (its overview, or a section: auth, storage, flags, …)
palbase open [section]
palbase init
Start a backend on palbase/backend here (or in <dir>): a health route, its module, its tests, a Dockerfile; --db adds a schema, its routes and their test
palbase init [dir] [--db] [--print]
Nothing already there is written over: a file init would write that exists stops it, naming the file.
--db's table is typed in palbase/palbase-env.d.ts by the block palbase db:types writes of the schema: run db:types again when you change it.
--print writes nothing: it prints the files as JSON ({ name, db, version, files: { <path>: <text> } }), for a program that seeds a repository with this release's starter (bunx palbase@<version> init --print <name> --db).
Then: bun install, palbase use <project>/<environment>, palbase env:pull, bun dev.
Its tests: bun test (here, no app); palbase test --users alice,bob --base-url http://localhost:8080 -- bun test ./tests (against the app you started, as test users made for the run); on the platform, palbase machines:update <name> --test-command "bun test ./tests" --test-users alice,bob runs them against each new build before it takes traffic.
--db with a database: a schema file (a table each user reads alone) and its routes
--print print the files as JSON, write nothing ([dir] names the package)
palbase strings
Keep the app's translations (palbase/strings/) with its code: each t("…") a sentence, every language's file with it
palbase strings [--source <language>] [--add <language>] [--translate] [--prune]
Reads every t("…") the backend's source calls — imported from palbase/backend, or from a module of the app that re-exports it — in the app's directory (the nearest one up with a package.json). A sentence new in the code is added to each language's file, empty: t() answers it in the source language until it is translated. A translation is kept.
A sentence the code no longer calls is kept and listed; --prune takes it out of every file. A call it could not follow (a t from a module it does not read, a linked directory) is said, where.
palbase/strings/_meta.json names the source language (the one the sentences are written in); each other language is palbase/strings/<language>.json, "sentence": "translation". Edit them as any file of the app.
--translate has the environment's ai module translate what is empty (the project's AI use, as any other: its cost is in the AI usage; production's asks first). A translation is used as it is written; correct it in the file.
A table in the earlier format (version 2) is written in this one.
--source <language> the language the sentences are written in, for a table not made yet (tr, en, pt-BR)
--add <language> a language to translate into: its file is made (may be given more than once)
--translate translate what is empty, with the environment's ai module (-P/-E, or palbase use)
--prune take the sentences the code no longer calls out of every file
palbase run
Run a command with the environment's address and your own key in its environment, nothing written to disk
palbase run [--secrets development|production] -- <command> [args…]
palbase run -- bun dev, palbase run -- npm test: the app reads the project's secrets at boot (PALBASE_SECRETS).
--secrets <set> the project's secrets the app reads at boot: development (default) or production
palbase test
Run the backend's tests as the platform does before a build goes out: test users made for the run and deleted after, PALBASE_TEST_* in the command's environment
palbase test [--machine <name>] [--users <names>] [--base-url <url>] [--secrets development|production] [-- <command> [args…]]
palbase test --machine backend --base-url http://localhost:8080: the machine's own test command and test users, as the platform runs them before each build of it rolls out. palbase test --users alice,bob --base-url http://localhost:8080 -- bun test: api.signInAs("alice") acts as a test user made for this run. The command gets what palbase run gives it, and PALBASE_TEST_AUTH_URL (the environment's address), PALBASE_TEST_IDENTITIES (the run's test users, each with its token) and, with --base-url, PALBASE_TEST_BASE_URL (where the app under test answers; without it HTTP tests skip themselves). The test users are deleted when the command ends, however it ends — Ctrl-C too: the command is stopped, they are deleted, and palbase test ends as the signal would have.
--machine <name> a machine whose test command (when no command is given) and test users (when --users is not) are run
--users <names> the test users to make for the run, comma-separated
--base-url <url> where the app under test answers (http://localhost:8080)
--secrets <set> the project's secrets the app reads at boot: development (default) or production
palbase billing
An organization's card and each environment's plan, balance, month and limit
palbase billing [--org <org>]
A member reads the plans and what was used; the money (balances, amounts) is an owner's or an admin's.
--org <org> the organization (name or id; the one of the environment chosen here, or your only one)
palbase journeys
The environment's journeys: each one's key, name, status, way in, and when it was published
palbase journeys [--status draft|live|stopped]
--status <status> draft, live or stopped alone
env
palbase env:pull
Write what the backend needs to run here into .env.local: the environment's address and your own key — no secret's value
palbase env:pull [--file <path>] [--secrets development|production]
The app reads the project's secrets at boot (PALBASE_SECRETS); their names are listed as comments: set one in the file to override it. The key is yours (CLI — you (this computer)), good for 30 days, made again by env:pull and run; palbase logout revokes it. Only the block between the palbase markers is written; the rest of the file stays as it is.
--file <path> the file (default .env.local)
--secrets <set> the project's secrets the app reads at boot: development (default) or production
palbase env:tokens:list
The environment's live tokens, by their hint (never their values)
palbase env:tokens:list
palbase env:tokens:create
Mint a token of the environment for a program (an AI agent): your role in this environment alone, a few hours; shown this once
palbase env:tokens:create [--scope <scope>[,<scope>]] [--minutes <n>] [--name <name>]
It does in this environment what your role does — as your own CLI would — but never makes or shows a standing credential (a key, a token), changes people or roles, or deletes a project or an environment; nothing beyond this environment. --scope narrows it to: machines:read, machines:logs, apps:sync, auth:test-users, support — machines:read reads the machines, their builds, crons and queues; machines:logs their logs; apps:sync what palbase apps:sync reads; auth:test-users makes and deletes test users; support asks Palbase's support line (palbase support:ask). Give it to the program as PALBASE_TOKEN: the CLI acts in this environment alone with it. It ends after --minutes (default 60, at most 240), and with what minted it.
--scope <scope> narrow it to: machines:read, machines:logs, apps:sync, auth:test-users, support (comma-separated, or given again); none: your role
--minutes <n> how long it lives (default 60, at most 240)
--name <name> what it is for, as the environment lists it
palbase env:tokens:revoke
Revoke an environment's token (by its id; with PALBASE_TOKEN an environment's token, self revokes it): refused from its next request on
palbase env:tokens:revoke <id|self>
types
palbase types:pull
Write the environment's secret, flag, bucket and role names into palbase/palbase-env.d.ts as types: Secrets.get, Flags and Storage.buckets take them and no other
palbase types:pull [--file <path>]
The names of the production secrets — what the machines start with, and so what Secrets.get reads there; development's are a developer's — leaving out what the platform sets itself (PALBASE_SECRET_KEY …), every flag, each bucket with its renditions (Storage.buckets.<name>.getPublicUrl(path, { variant })) and every role — never a value.
Only the block between the palbase:stack markers is written; the rest of the file stays as it is. Run it again when a name is added or taken away.
--file <path> the file (default palbase/palbase-env.d.ts)
orgs
palbase orgs:list
Your organizations and your role in each
palbase orgs:list
palbase orgs:create
Make an organization; you are its owner
palbase orgs:create <name>
palbase orgs:members:list
An organization's members, and the invitations still open (for its admins)
palbase orgs:members:list
--org <org> the organization (name or id); needed when you are in more than one
palbase orgs:members:invite
Invite someone to an organization by email; they accept in Studio
palbase orgs:members:invite <email> [--role admin|member]
--org <org> the organization (name or id)
--role <role> admin or member (default member)
palbase orgs:members:remove
Take a member (by email or user id) or an open invitation out of an organization
palbase orgs:members:remove <email|user-id>
--org <org> the organization (name or id)
palbase orgs:audit
Who changed what at an organization's environments, through Studio or the CLI, newest first — its admins'
palbase orgs:audit [--org <org>] [--limit <n>]
A module keeps a change made through Studio as Studio's key's: this says whose it was. What was sent is not kept.
--org <org> the organization (name or id)
--limit <n> how many at most (default 50)
tokens
palbase tokens:list
An organization's access tokens (PALBASE_TOKEN for CI and agents): name, role, when they end and were last used — never their values
palbase tokens:list
--org <org> the organization (name or id)
palbase tokens:create
Make an access token of an organization for CI or an agent; its value is shown this once
palbase tokens:create <name> [--role member|admin] [--days <n>]
Give it to the CI as the secret PALBASE_TOKEN: the CLI acts with it in place of a sign-in. It reaches its organization's projects and environments at its role, and nothing of its people or tokens. It ends after --days (default 90, at most 365), and goes when it is revoked or when the one who made it leaves the organization.
--org <org> the organization (name or id)
--role <role> member (default) or admin: what it may change
--days <n> how many days it is good for (default 90)
palbase tokens:revoke
Revoke an organization's access token (by its name or id): refused from its next request on
palbase tokens:revoke <name|id>
--org <org> the organization (name or id)
billing
palbase billing:charges
A month's charges, by environment and item — used, at the price table, charged past each one's free quota — as a table or as CSV
palbase billing:charges [--period <YYYY-MM>] [--csv] [--org <org>]
--csv prints the CSV Studio downloads: what an organization bills its own customers by.
--org <org> the organization (name or id; the one of the environment chosen here, or your only one)
--period <YYYY-MM> the month (this one by default)
--csv print the charges as CSV
palbase billing:invoices
The invoices of an organization's top-ups, each of an environment: Stripe makes one of each, and emails it
palbase billing:invoices [<project/env | ref>] [--org <org>]
--org <org> the organization (name or id; the one of the environment chosen here, or your only one)
palbase billing:ledger
Every change of an environment's balance, newest first: top-ups, usage taken, credit moved, refunds, what expired
palbase billing:ledger [<project/env | ref>] [--limit <n>]
--limit <n> how many at most (default 50)
palbase billing:env
An environment's place in the bill — its plan, frozen, its month's limit, its automatic top-up — read, or changed
palbase billing:env [<project/env | ref>] [--plan free|pay_as_you_go] [--limit <usd>|none] [--frozen yes|no] [--auto-topup yes|no] [--threshold <usd>] [--amount <usd>] [--low-balance-days <n>] [-y]
With no flag it shows the environment: its plan, balance, automatic top-up and month. Each environment pays for itself: at its limit, or its balance run out, it is Free until the month ends or a top-up comes; the organization's other environments go on. Moved to Pay as you go it needs credit to pay from: its first top-up (palbase billing:topup) moves it. Frozen, its requests are refused and its machines and databases stopped, its data kept; only what it stores is charged. Freezing asks first; -y does not.
--plan <free|pay_as_you_go> Free (charged nothing, held to the free quotas, its credit kept) or Pay as you go (paid from its balance)
--limit <usd|none> at most this much a month; none for no limit
--frozen <yes|no> frozen: its requests refused, its machines and databases stopped, its data kept
--auto-topup <yes|no> take a top-up from the organization's card when the balance is under the threshold
--threshold <usd> the balance under which it is taken
--amount <usd> how much it takes ($10 at least)
--low-balance-days <n> email the owners when the balance lasts fewer than n days of spend (1 to 60)
palbase billing:topup
Put money on an environment's balance now — from what the organization's bank transfers left, else its saved card: $10 to $10,000, the tax on top, an invoice made of it
palbase billing:topup [<project/env | ref>] <usd> [-y]
It takes the money: it asks first, and -y does not. An environment on Free is moved to Pay as you go by it (its first top-up). With no card saved yet, the first top-up is on Stripe's page, in Studio.
palbase billing:bank-transfer
An invoice of an environment's top-up to pay by bank transfer, to the organization's own bank details: credited as the money arrives
palbase billing:bank-transfer [<project/env | ref>] <usd>
Stripe emails the invoice with the bank details; nothing is taken until the transfer arrives. A transfer of more than the invoice stays with the organization and pays its environments' next top-ups, before any card — for 75 days, then Stripe sends what is left back.
palbase billing:transfer
Move credit from one of an organization's environments to another — all of it, or an amount — each part with its expiry
palbase billing:transfer <from project/env | ref> <to project/env | ref> [<usd>] [-y]
An owner's or an admin's (an admin's access token too); never to another organization's environment. It asks first, and -y does not. A deleted environment keeps its credit until it is purged (a week): move it before then, or it is lost.
projects
palbase projects:list
Every project of your organizations, with its environments
palbase projects:list
palbase projects:create
Make a project with its first environment (main), and print its keys
palbase projects:create <name> [--org <org>] [--env-name <name>]
The first environment is the project's production one: its slug is always main. Its secret key is printed this once; keys:create makes more.
--org <org> the organization (name or id)
--env-name <name> what the first environment is called on screen (default Production)
palbase projects:delete
Delete a project and every environment of it
palbase projects:delete <project> [-y]
Its environments' data goes with them (the platform keeps it a week to bring back).
envs
palbase envs:list
A project's environments (the project chosen here, or -P)
palbase envs:list
palbase envs:create
Add an environment to a project (staging, a developer's own)
palbase envs:create <slug> [--name <name>] [--from <env> [--with-cloud]]
Each environment is a project of its own on the platform: its users, data, machines and keys are its own. --from opens it with another environment's module settings (palbase config:copy: secrets are named, not copied); --with-cloud its machines and databases too — declared alike, built from the same branches, with no data (palbase db:copy brings it).
--name <name> what it is called on screen (default: the slug)
--from <env> open it with this environment's settings (slug or ref)
--with-cloud with --from: its machines and databases too (the new environment's GitHub link builds the machines)
palbase envs:delete
Delete an environment of a project, with its data
palbase envs:delete <slug>
keys
palbase keys:list
The environment's API keys (never their values)
palbase keys:list
palbase keys:create
Make an API key of the environment; its value is shown this once
palbase keys:create --kind publishable|secret [--label <label>]
--kind <kind> publishable (ships in apps) or secret (servers only)
--label <label> what it is for, as Studio lists it
palbase keys:revoke
Revoke an API key of the environment; what holds it is refused from then on
palbase keys:revoke <key-id>
auth
palbase auth:settings:get
The environment's auth settings: passwords, email confirmation, sessions, tokens, passkeys, risk
palbase auth:settings:get
palbase auth:settings:set
Change auth settings: key=value, a value read as JSON when it is JSON (12, true, ["https://…"])
palbase auth:settings:set <key=value>…
Text needs no quotes (site_url=https://shop.example); quote text that reads as JSON: from_name='"123"'. The change is made on the revision read: one made beside it in the meantime (Studio, another terminal) is refused, not overwritten. palbase auth:settings:get lists the keys.
palbase auth:providers:list
The sign-in methods: email, phone and the social providers, whether each is on, their clients and stored secrets (by name)
palbase auth:providers:list
palbase auth:providers:set
Turn a sign-in provider on or off and set it up: its client, its secret (from stdin), phone codes
palbase auth:providers:set <provider> [--enable|--disable] [setting=value…] [--client-id <id>] [--secret-stdin] …
email is always on. phone: --enable/--disable, and its code settings as setting=value (allow_signups, otp_ttl_seconds, otp_max_attempts, sender, sms_second_factor). google, apple, github, microsoft: --client-id sets the browser client --client names (default web), made when the provider has none of that name. --secret-stdin reads the client's secret from stdin (Apple: the .p8 signing key, with --team-id and --key-id) and keeps it under --credential (default: the provider's name). It is never taken from the command line and never printed. --clients-file replaces the provider's clients with the file's {"browser_clients": […], "native_clients": […]} (native apps, several clients).
--enable turn it on
--disable turn it off
--client <key> the browser client --client-id, --return-uri and the secret go to (default web)
--client-id <id> the provider's id of your app (Google web client id, Apple services id, Microsoft application id, GitHub client id)
--return-uri <url> where your app is sent back after sign-in (repeat it; replaces the list)
--application <key> the application the client is for (default app)
--variant <name> the build variant the client is for (default release)
--directory <tenant> microsoft: a tenant id, organizations, consumers or common (default common)
--secret-stdin read the client's secret (Apple: the .p8 key) from stdin
--credential <key> the name the secret is kept under (default: the provider's)
--team-id <id> apple: your team's id, with --secret-stdin
--key-id <id> apple: the signing key's id, with --secret-stdin
--clients-file <path> replace the provider's clients with the file's (JSON)
palbase auth:providers:disable
Turn a sign-in provider off (phone, google, apple, github, microsoft); its clients and secrets stay
palbase auth:providers:disable <provider>
palbase auth:providers:secret:delete
Delete a sign-in provider's stored secret by its name; one a client still uses is refused
palbase auth:providers:secret:delete <name>
palbase auth:roles:list
The environment's roles: their permissions, which one new users get, how many users hold each
palbase auth:roles:list
palbase auth:roles:set
Make a role, or change one: its permissions (the list is replaced), description, whether new users get it
palbase auth:roles:set <name> [--permissions <a.b,c.d>] [--description <text>] [--default yes|no]
--permissions <list> resource.action permissions, comma-separated or repeated; replaces the list ("" for none)
--description <text> what the role is for
--default <yes|no> whether new users get it (one role at most)
palbase auth:roles:delete
Delete a role; one users hold is deleted only with --force, and is taken from them
palbase auth:roles:delete <name> [--force]
--force delete it although users hold it
palbase auth:hooks:list
The blocking hooks: what auth calls, and waits for, before a sign-up, a sign-in, a token
palbase auth:hooks:list
palbase auth:hooks:create
Register a blocking hook; its signing secret is printed this once
palbase auth:hooks:create --event <event> --url <url> [--failure-mode deny|allow] [--timeout <ms>] [--disable]
--event <event> before.user.create, before.login, before.token.issue, … (the module lists them)
--url <url> the https endpoint auth calls and waits for
--failure-mode <deny|allow> what auth does when the hook does not answer in time (default deny)
--timeout <ms> how long auth waits, 100–5000 ms (default 5000)
--disable register it turned off
palbase auth:hooks:update
Change a blocking hook: its event, endpoint, timeout, failure mode, on or off (its secret stays)
palbase auth:hooks:update <id> [--event …] [--url …] [--failure-mode …] [--timeout …] [--enable|--disable]
--event <event> before.user.create, before.login, before.token.issue, … (the module lists them)
--url <url> the https endpoint auth calls and waits for
--failure-mode <deny|allow> what auth does when the hook does not answer in time (default deny)
--timeout <ms> how long auth waits, 100–5000 ms (default 5000)
--enable turn it on
--disable turn it off
palbase auth:hooks:delete
Delete a blocking hook; auth stops calling it
palbase auth:hooks:delete <id>
palbase auth:oauth-clients:list
The OAuth clients of the environment's OpenID provider (apps that sign in with your users' accounts)
palbase auth:oauth-clients:list
palbase auth:oauth-clients:create
Register an OAuth client; a confidential client's secret is printed this once
palbase auth:oauth-clients:create <name> [--public] [--redirect-uri <url>…] [--grant <type>…] [--scope <scope>…] [--client-id <id>]
--public a public client (a CLI, a mobile app): it has no secret
--redirect-uri <url> where it may send the browser back (repeat it)
--grant <type> authorization_code (default), refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:device_code
--scope <scope> what it may ask for (default openid, profile, email)
--client-id <id> a client_id to use as it is (default: a random one)
palbase auth:oauth-clients:delete
Delete an OAuth client (by its id or its client_id)
palbase auth:oauth-clients:delete <id|client-id>
palbase auth:import
Bring users over with what they sign in with: ids, addresses, password hashes, providers, second factors, passkeys, open sessions
palbase auth:import <file|folder> [--pepper-stdin]
A folder holds import bodies, one a file (users-0001.json…, each without its pepper); a file holds one such body, JSON lines (a user each) or one JSON array. Password and recovery-code hashes keep the source's pepper: give it on stdin with --pepper-stdin — never on the command line. Users go 1000 a call. One refused does not stop the others; a user whose id or address the project has is refused, never overwritten.
--pepper-stdin read the source's pepper from stdin
palbase auth:users:list
The environment's users: id, email, verified, last sign-in; --banned for the banned alone
palbase auth:users:list [--banned] [--limit <n>]
--banned only the banned
--limit <n> how many at most (default 100)
palbase auth:users:get
One user: their email and phone, how they sign in, whether verified or banned, their roles
palbase auth:users:get <user-id>
palbase auth:users:create
Make a user with an email and a password (read from stdin, never the command line)
palbase auth:users:create <email> [--verified]
--verified their email is taken as verified
palbase auth:users:delete
Delete a user: their account, sessions and sign-ins go; backends hear user.deleted
palbase auth:users:delete <user-id>
palbase auth:users:ban
Ban a user: their sessions end and they cannot sign in until unbanned
palbase auth:users:ban <user-id> [--reason <text>]
--reason <text> why (kept with the ban)
palbase auth:users:unban
Let a banned user sign in again
palbase auth:users:unban <user-id>
palbase auth:users:sign-out
End every session of a user's: each device signs in again
palbase auth:users:sign-out <user-id>
palbase auth:users:export
Everything the environment holds about one user, as the law lets them ask for it (GDPR art. 15/20): JSON
palbase auth:users:export <user-id> [--out <file>]
--out <file> write it here instead of to standard output
palbase auth:audit
Who did what in the environment's auth, newest first: sign-ins, changes to users and settings, admin actions
palbase auth:audit [--event <type>] [--limit <n>]
--event <type> only this kind of event (user.login, admin.user.update…)
--limit <n> how many at most (default 50)
palbase auth:audit:verify
Whether the audit trail is whole: each event chained to the one before, none changed or taken out
palbase auth:audit:verify
palbase auth:audit:export
The audit trail as a file: JSON lines or CSV, a span of it, one kind of event
palbase auth:audit:export [--format ndjson|csv] [--from <time>] [--to <time>] [--event <type>] [--out <file>]
--format <ndjson|csv> JSON lines (default) or CSV
--from <time> from this time on (RFC 3339: 2026-10-01T00:00:00Z)
--to <time> up to this time
--event <type> only this kind of event
--out <file> write it here instead of to standard output
palbase auth:signing-keys
The keys the environment's tokens are signed with: the active one, those still verifying, the retired
palbase auth:signing-keys
palbase auth:signing-keys:rotate
Sign with a new key from now on; tokens the old one signed go on verifying until they end
palbase auth:signing-keys:rotate
palbase auth:test-users:list
The environment's test users (made with its secret key; their tokens say test: true)
palbase auth:test-users:list
palbase auth:test-users:create
Make test users for a suite: their passwords, and with --token an access token each, shown this once; --template has the backend write the data a screen needs for each
palbase auth:test-users:create [--count <n>] [--token] [--template <name> [--machine <name>]]
A test user signs in as any user does; its tokens say test: true, so a backend can tell them apart. --token gives each an access token at once, for a test that calls the backend as that user.
--template <name> has the backend write the data of its @TestUserTemplate({ name }) for each user — its seed for one account, in its own code (palbase/backend), run on the machine --machine names or the environment's only one, for test users alone. Each user is answered with the rows written, by table, or why its template was not (no_template, not_a_test_user, template_failed, template_timeout, machine_unavailable): the user is made all the same, and the command then ends with 1. At most 10 users at once with a template.
palbase auth:test-users:template writes another template for a user made with one; palbase auth:test-users:delete has the template undone before the user is deleted.
--count <n> how many (default 1, at most 100; 10 with --template)
--token an access token for each, too
--template <name> a @TestUserTemplate of the backend's: its data written for each user
--machine <name> the machine whose backend has the template (default: the environment's only one)
palbase auth:test-users:template
Have the backend write a @TestUserTemplate's data for a test user made with a template — the same one again answers what it wrote then
palbase auth:test-users:template <user-id> <template> [--machine <name>]
The user is one auth:test-users:create --template made on that machine: a test user is the machine's that made it. Asked again for a template it wrote, the platform answers what it wrote then, and the backend is not called.
--machine <name> the machine whose backend has the template (default: the environment's only one)
palbase auth:test-users:delete
Delete a test user — one made with a template has the backend undo it first
palbase auth:test-users:delete <user-id> [--machine <name>]
A test user made with --template is deleted through the machine that made it: the backend's undo of each template runs first — a template with none leaves its data to user.deleted (what ownedByUser() owns goes with the user) — and an undo that fails keeps the user, for the delete to be asked again. Any other test user is deleted at once. Without --machine the environment's machines are asked which made the user; a token that does not list the machines deletes it at once, its templates left to user.deleted.
--machine <name> the machine that made the user with a template (default: whichever did)
storage
palbase storage:buckets:list
The environment's buckets: who may read each, the largest file and the types it takes
palbase storage:buckets:list
palbase storage:buckets:get
A bucket's declaration in full, with its renditions
palbase storage:buckets:get <bucket>
palbase storage:buckets:set
Make a bucket, or change one: public or private, the largest file, the content types, its renditions
palbase storage:buckets:set <bucket> [--public|--private] [--max-size <size>] [--types <a/b,c/*>] [--variants-file <path>]
What is not given is kept as it is (a new bucket: private, the platform's size, any type, no rendition).
--public anyone may read its files at /storage/public/
--private its files are read with signed URLs and the rules
--max-size <size> the largest file: 10MB, 512KiB, 1GB, bytes, or platform
--types <list> the content types it takes, comma-separated (image/* a family); "" for any
--variants-file <path> its renditions, a JSON list of {name, width, height, fit, format, quality}
palbase storage:buckets:delete
Delete an empty bucket (one that still holds files is refused)
palbase storage:buckets:delete <bucket>
palbase storage:ls
A bucket's objects, or those under a prefix: path, size, type
palbase storage:ls <bucket>[:prefix]
palbase storage:cp
Copy files between this computer and a bucket: a file, or a folder whole with -r
palbase storage:cp <from> <to> [-r] [--owner <user id>]
bucket:path names an object of the environment; anything else is a path on this computer. Up: a file to bucket:path (a path ending in / keeps the file name); with -r, a folder to bucket:prefix, its paths kept. Down: bucket:path to a file; with -r, every object under bucket:prefix into a folder.
-r, --recursive a folder (up) or a prefix (down), whole
--owner <user id> the user uploaded objects belong to (deleted with them)
palbase storage:rm
Delete an object, or every object under a prefix with -r
palbase storage:rm <bucket:path> [-r]
-r, --recursive every object under the prefix
palbase storage:url
A signed download URL for one object
palbase storage:url <bucket:path> [--expires <seconds>]
--expires <seconds> how long it works (default 3600, at most 604800)
flags
palbase flags:list
The environment's flags as they are live: type, default, targeting (and the draft's state when it publishes)
palbase flags:list
palbase flags:set
Define a flag or change its default and targeting (into the draft when the environment publishes)
palbase flags:set <key> <value> [--type bool|number|string|object] [--condition "<expression> => <value>"…] [--description <text>]
The value is read as the flag's type: true/false, a number, text, or a JSON object; a new flag's type is the value's unless --type says it. --condition replaces the targeting (first match wins, 8 at most); --clear-conditions takes it away; without either it stays. An environment that publishes takes the change into its shared draft (palbase flags:publish makes it live); one that does not, live at once.
--type <type> bool, number, string or object
--condition <rule> "<expression> => <value>" (repeat it, in order)
--clear-conditions take its targeting away
--variants <list> a string flag's allowed values, comma-separated
--description <text> what the flag is for
palbase flags:delete
Delete a flag, with every user's own value of it (from the draft when the environment publishes)
palbase flags:delete <key>
palbase flags:publish
Show the flags draft's changes against the live version, then publish it as the next version
palbase flags:publish [-m <note>]
-m, --note <note> why: kept with the version
palbase flags:versions
The published versions of the flags, newest first, and which one is live
palbase flags:versions [--limit <n>]
--limit <n> how many (default 20)
palbase flags:rollback
Make an earlier version of the flags live again (published as the next version)
palbase flags:rollback <version> [-m <note>]
-m, --note <note> why (default: rollback to <version>)
palbase flags:publishing:enable
Turn publishing on for the environment's flags: changes go into a shared draft, live once published — for good
palbase flags:publishing:enable
What is live now becomes version 1. From then on flags:set and flags:delete change the draft, flags:publish makes it live as the next version, and flags:rollback brings an earlier one back. It cannot be turned off.
palbase flags:users:get
Every flag as one user gets it now — their own values, the targeting, the defaults — as their client reads them
palbase flags:users:get <user-id>
palbase flags:users:set
Give a user a flag's value of their own, whatever its default and targeting say
palbase flags:users:set <user-id> <key> <value>
palbase flags:users:unset
Take a user's own value of a flag away, or every one with no key: the flag decides for them again
palbase flags:users:unset <user-id> [key]
notify
palbase notify:providers:list
The environment's own providers, by channel and the order they are tried (never their credentials)
palbase notify:providers:list
palbase notify:providers:set
Bring a provider for a channel: its credentials are piped in as JSON, checked as the sender will use them, never shown
palbase notify:providers:set <channel> <provider> [--priority <n>] [--enable|--disable] < credentials.json
email: acs {connection_string, from_email}, ses {region, access_key_id, secret_access_key, from_domain}, sendgrid {api_key, from_domain}, smtp {host, port, username, password, from_email, use_starttls}. sms: twilio {account_sid, auth_token, from_number}, vonage {api_key, api_secret, from}, acs {connection_string, from_number}. verify: twilio_verify {account_sid, auth_token, service_sid}. whatsapp: meta {phone_number_id, access_token, api_version, app_secret, verify_token}. push: fcm (the service account JSON), apns {private_key, team_id, key_id, bundle_id, is_production}, expo {access_token}. The credentials are read from stdin only: never from the command line, where the shell would keep them.
--priority <n> lower is tried first
--enable turn it on
--disable turn it off
palbase notify:providers:remove
Forget a provider and its credentials
palbase notify:providers:remove <channel> <provider>
palbase notify:templates:list
The templates messages render from: the platform's defaults and the ones the environment overrides
palbase notify:templates:list [--channel email|sms|whatsapp]
--channel <channel> only this channel's (default: all three)
palbase notify:templates:get
A template as it renders now in a locale (yours, or the platform's default); --json gives the document templates:set takes
palbase notify:templates:get <slug> --locale <locale> [--channel email|sms|whatsapp]
--channel <channel> email (default), sms or whatsapp
--locale <locale> the template's locale: en, tr, pt-BR
palbase notify:templates:set
Override a template in a locale, from a file or stdin (email: {subject, html_body, text_body} as JSON)
palbase notify:templates:set <slug> --locale <locale> [--channel email|sms|whatsapp] [--file <path>]
email: {"subject", "html_body", "text_body"}; sms: the text, or {"body"}; whatsapp: {"name", "language", "variables", "code_button"} of a template approved on your Meta account. notify:templates:get … --json > t.json gives a document to edit and set back. Handlebars: {{name}}, {{#if}}, {{#each}}.
--channel <channel> email (default), sms or whatsapp
--locale <locale> the template's locale: en, tr, pt-BR
--file <path> the template (default: stdin)
palbase notify:templates:delete
Remove an override: the platform's default renders again (a WhatsApp mapping is forgotten)
palbase notify:templates:delete <slug> --locale <locale> [--channel email|sms|whatsapp]
--channel <channel> email (default), sms or whatsapp
--locale <locale> the template's locale: en, tr, pt-BR
palbase notify:topics:list
The topics users follow, how many follow each, whether they follow it from the app
palbase notify:topics:list
palbase notify:topics:create
Make a topic users can follow; a push to it reaches its followers
palbase notify:topics:create <name> [--self-subscribe yes|no]
--self-subscribe <yes|no> users follow it from the app (default no: the project adds them)
palbase notify:topics:delete
Delete a topic and who follows it
palbase notify:topics:delete <name>
palbase notify:settings:get
The environment's message settings: the default locale, who email is from, where texts may go
palbase notify:settings:get
palbase notify:settings:set
Change message settings: key=value (from_email=…, sms_countries=["+90","+44"], mx_check=true)
palbase notify:settings:set <key=value>…
The change is made on the revision read: one made beside it in the meantime is refused, not overwritten.
palbase notify:send
Send an email or a text through the environment's providers: written here, or a template with its variables
palbase notify:send <email|sms> <to> [--template <slug> --var name=value…] [--subject <s> --text <t> --html <h>] [--body <text>]
A message is sent as a backend sends it (POST /notify/email, /notify/sms): its delivery is in palbase notify:messages.
--template <slug> a template of the environment (notify:templates:list)
--var <name=value> a template's variable (again for each)
--locale <locale> the template's locale
--subject <text> an email's subject
--text <text> an email's text
--html <html> an email's HTML
--body <text> a text's words
palbase notify:messages
The messages the environment sent, newest first, and what became of each
palbase notify:messages [--limit <n>]
--limit <n> how many (default 20)
palbase notify:deliverability
Whether each channel can send now, through which providers, and what to do when it cannot
palbase notify:deliverability
realtime
palbase realtime:channels:get
The channel declaration, in the order patterns are matched: who may join each, publish, read and write its state
palbase realtime:channels:get
palbase realtime:channels:set
Replace the channel declaration with a JSON file or stdin ({"channels": […]} or the list)
palbase realtime:channels:set [--file <path>]
Each channel: {"pattern": "room:{id}", "kind": "public", "publish": true, "state_read": true, "state_write": false}, or {"pattern": "user:{uid}", "kind": "owner"}. realtime:channels:get --json gives the document to edit. Open sockets are decided again within a heartbeat.
--file <path> the declaration (default: stdin)
palbase realtime:limits:get
The limits the environment holds its clients to (null: the platform's), and the platform's
palbase realtime:limits:get
palbase realtime:limits:set
Change realtime limits: key=value (max_connections=500), null for the platform's
palbase realtime:limits:set <key=value>…
The keys: max_connections, max_events_per_second, max_joins_per_second, max_channels_per_connection. A value above the platform's has no effect beyond it.
palbase realtime:broadcast
Send an event to a channel's subscribers, as the server does: its payload JSON (or text)
palbase realtime:broadcast <topic> <event> [payload]
analytics
palbase analytics:settings:get
How long analytics keeps events and console records, the share of performance samples kept, whether bodies are kept
palbase analytics:settings:get
palbase analytics:settings:set
Change analytics settings: key=value (retention_days=30, perf_sample_pct=10, console_bodies=false)
palbase analytics:settings:set <key=value>…
A shorter retention removes what is older within minutes.
palbase analytics:events
The events analytics recorded, newest first: by name, by user, over a window
palbase analytics:events [--name <event>] [--user <distinct-id>] [--window <24h>] [--limit <n>]
--name <event> only this event's
--user <id> only this user's (their distinct id)
--window <span> how far back: 30m, 24h, 7d (default 24h)
--limit <n> how many (default 50)
palbase analytics:summary
What analytics counted over a window: events, users, sessions, the top events
palbase analytics:summary [--window <24h>]
--window <span> how far back: 30m, 24h, 7d (default 24h)
messaging
palbase messaging:settings:get
How the environment runs its conversations: how long messages are kept, whether a push says what one says, what becomes of a deleted user's messages, the hook
palbase messaging:settings:get
palbase messaging:settings:set
Change how the environment runs its conversations: a setting left out is left as it is
palbase messaging:settings:set [--retention-days <days>|off] [--push-preview on|off] [--deleted-user-messages keep|delete]
A shorter retention removes the messages older than it, in every conversation. A deleted user's messages are kept or deleted as the setting is when the user is deleted. Deleted, what of theirs had already gone from screens (seen once, its time up, vanish mode, taken back) stays its 14 days, sealed, for reports, then goes. Only what changes is sent: a change made beside this one to another setting stays.
--retention-days <days|off> messages older than this many days are removed (1–36500); off: kept for good
--push-preview <on|off> a push says what the message says (on), or only that one came (off)
--deleted-user-messages <keep|delete> a deleted user's messages: kept, their writer anonymous, or deleted (what went from screens before stays its 14 days for reports)
palbase messaging:hook:get
The hook asked whom one may reach: where it is, how long it is waited for, what is done when it does not answer (never its secret)
palbase messaging:hook:get
palbase messaging:hook:set
Set the hook asked whom one may reach, or change it; a new hook's signing secret is printed this once, or kept in a secret with --secret-to
palbase messaging:hook:set [--url <url>] [--timeout-ms <ms>] [--on-error allow|deny] [--secret-to <NAME> [--dev]]
Before a direct conversation opens or people are added to a group, the hook is POSTed {"event": "messaging.can_message", "action": "direct" | "group_add", "from_user_id", "to_user_ids", "conversation_id"}, signed as Standard Webhooks sign (webhook-id, webhook-timestamp, webhook-signature), and answers 200 {"allowed_user_ids": […]}. A new hook takes --url (an https URL on the public internet). Its signing secret is printed this once, or kept in the environment's secrets with --secret-to (production; --dev: development) and printed nowhere. A hook changed keeps its secret: palbase messaging:hook:secret:rotate makes a new one.
--url <url> where the hook is asked
--timeout-ms <ms> how long it is waited for: 100 to 5000 (a new hook: 2000)
--on-error <allow|deny> when it does not answer: allow everyone, or deny and the app is told to try again (a new hook: deny)
--secret-to <NAME> keep the new signing secret in the environment's secret NAME instead of printing it
--dev with --secret-to: the development secrets, not production
palbase messaging:hook:remove
Remove the hook: anyone may reach anyone, and its signing secret is forgotten
palbase messaging:hook:remove
palbase messaging:hook:secret:rotate
Make the hook's signing secret anew: printed this once, or kept in a secret with --secret-to; the old one signs nothing more
palbase messaging:hook:secret:rotate [--secret-to <NAME> [--dev]]
The hook's calls are signed with the new secret alone from now: the endpoint verifies them with it (a machine reads a secret kept with --secret-to at its next start).
--secret-to <NAME> keep the new signing secret in the environment's secret NAME instead of printing it
--dev with --secret-to: the development secrets, not production
palbase messaging:reports:list
The reports people made, newest first: why, whom, which messages (what they carry: messaging:reports:get)
palbase messaging:reports:list [--status open|resolved] [--limit <n>] [--cursor <cursor>]
--status <status> only the open ones, or only the resolved ones
--limit <n> at most this many (1–100, default 30)
--cursor <cursor> the next page, as the last one said it
palbase messaging:reports:get
One report with what it carries: the reporter's note, what the messages it names said when reported and before they were edited
palbase messaging:reports:get <report id>
An owner's or an admin's (a member lists the reports, without what they carry); the read is in the project's access log. --json carries the messages' files with the keys that open them. A report of a group a person deleted says who deleted it and when; naming no message, it carries the last 30 messages the reporter saw in it. A report is deleted, with what it carries, 190 days after it is resolved; one never resolved, 2 years after it was made.
palbase messaging:reports:resolve
Resolve a report, saying what came of it in the project's own words
palbase messaging:reports:resolve <report id> --resolution <text>
--resolution <text> what came of it: 1 to 64 characters (warned, removed, nothing found…)
palbase messaging:access-log
Each time people's messages were read or removed — by the project's server, or an owner or admin from Studio or the CLI — newest first: what, which conversation, whose, by whom (kept 400 days)
palbase messaging:access-log [--limit <n>] [--cursor <cursor>]
--limit <n> at most this many (1–200, default 50)
--cursor <cursor> the next page, as the last one said it
palbase messaging:conversations:list
The project's conversations, or a person's with --user, the latest first: whose, how many people, when it last moved
palbase messaging:conversations:list [--user <user id>] [--limit <n>] [--cursor <cursor>]
--user <user id> only this person's conversations
--limit <n> at most this many (1–100, default 30)
--cursor <cursor> the next page, as the last one said it
palbase messaging:messages:list
What a conversation said, newest first — each read in the project's access log
palbase messaging:messages:list <conversation id> [--limit <n>] [--cursor <cursor>]
An owner's or an admin's: a member sees the conversations and the reports, not what was said. --json carries the messages whole, their files with the keys that open them.
--limit <n> at most this many (1–100, default 50)
--cursor <cursor> the next page, as the last one said it
palbase messaging:messages:delete
Remove a message for everyone in its conversation: what it said and its files go (in the access log)
palbase messaging:messages:delete <conversation id> <message id>
It is not undone: asked before it is done, whatever the environment (-y confirms).
config
palbase config:diff
How two environments' module settings differ (auth, storage, flags, notify, realtime, analytics, messaging; machines, databases when named)
palbase config:diff --from <env> --to <env> [--only <modules>]
- a key only in --from, + only in --to, ~ in both with other values. Ids, times and versions are not compared; a secret is compared as set or not, never by its value. Exit code 0 when they are the same, 1 when they differ. --json gives each difference with both values.
--from <env> the environment compared from (slug or ref)
--to <env> the environment compared to (slug or ref)
--only <modules> only these, comma-separated: auth,storage,flags,notify,realtime,analytics,messaging (the default), and databases,machines when named
palbase config:copy
Make one environment's module settings equal to another's (secrets excepted: their names are said)
palbase config:copy --from <env> --to <env> [--only <modules>]
It shows the differences, asks before changing a production environment (-y confirms), changes the target, and compares again. A secret is never copied: a provider's credentials and a sign-in client's secret are listed by name for you to set. A new hook or OAuth client gets its own secret, printed this once. What the target holds that its users depend on is not taken by a copy: a bucket with files, a role users hold, a topic users follow are left and said. --only machines,databases copies what they are declared as — a machine's source, size, copies, settings, secrets' name, database; a database's size, who may connect, backups — never a database's data (palbase db:copy) nor a secret's value. A machine or database only the target has is left serving. Exit code 0 when the two are the same afterwards, 1 when a difference remains.
--from <env> the environment compared from (slug or ref)
--to <env> the environment compared to (slug or ref)
--only <modules> only these, comma-separated: auth,storage,flags,notify,realtime,analytics,messaging (the default), and databases,machines when named
secrets
palbase secrets:list
The environment's secrets by name, with their version — never their values
palbase secrets:list [--dev]
--dev the development set (what a backend on a developer's computer reads), not production
palbase secrets:set
Set a secret: its value is read from stdin, never from the command line
palbase secrets:set <NAME> [--dev] < value
The environment's machines read production at their next start; a backend run with palbase run or env:pull reads development.
--dev the development set (what a backend on a developer's computer reads), not production
palbase secrets:get
Print one secret's value (an admin's)
palbase secrets:get <NAME> [--dev]
--dev the development set (what a backend on a developer's computer reads), not production
palbase secrets:unset
Take a secret away
palbase secrets:unset <NAME> [--dev]
--dev the development set (what a backend on a developer's computer reads), not production
palbase secrets:versions
A secret's versions, newest first — never their values
palbase secrets:versions <NAME> [--dev]
--dev the development set (what a backend on a developer's computer reads), not production
palbase secrets:rollback
Put a secret back to one of its versions (a new version with that value)
palbase secrets:rollback <NAME> --version <n> [--dev]
--dev the development set (what a backend on a developer's computer reads), not production
--version <n> the version to go back to
palbase secrets:audit
Every read and write of the environment's secrets, newest first: which secret, which key did it, when — never a value
palbase secrets:audit [--name <NAME>] [--dev | --prod] [--limit <n>]
A backend that reads the secrets at boot (palbase run, a machine) shows here as a read, with the key it read with: palbase keys:list names the key. An encrypted column's values opened through Palbase (Studio's table, a secret key) are recorded under the column's address: --name patients.tc lists who opened them, and when.
--name <NAME> only this secret's, or an encrypted field's opens (table.column)
--dev only the development secrets
--prod only the production secrets
--limit <n> how many (default 30)
fields
palbase fields:list
The keys the environment's encrypted database fields are sealed with — never the keys themselves
palbase fields:list
palbase fields:rotate
Make a new key for the environment's encrypted fields (an owner's or an admin's)
palbase fields:rotate
It comes into force in five minutes: by then every running backend has fetched it. New values are sealed with it from then on; a palbase/backend app seals its older values again with it, in the background. The older keys go on opening what they sealed.
palbase fields:keys
Download the environment's field keys into a file (its owner's): what opens a dump of its database elsewhere
palbase fields:keys --out <file>
The file opens every encrypted field of the environment: keep it as you would the database's password. It is the shape palbase/server's Fields reads: new Fields({ fetch: async () => JSON.parse(readFileSync(file, 'utf8')) }). The download is in the secrets audit trail (secrets:audit).
--out <file> where the keys go (made readable by you alone); never the screen
transit
palbase transit:list
The keys the encrypted fields of a request to the environment's backend are sealed to — their public halves alone
palbase transit:list
palbase transit:rotate
Make a new key for the encrypted fields of requests to the backend (an owner's or an admin's)
palbase transit:rotate [--retire-previous]
Apps seal to it from their next fetch of the project's keys (within the hour), or at once with --retire-previous: every older key is retired with it — what to do when one may have been taken — and an app that seals to a retired one is told so, fetches the keys again and sends its call once more. No app needs a new build: an app holds the project's signing key, not its keys.
--retire-previous retire every older key at once
palbase transit:retire
Retire a transit key that is not the one in use: no request sealed to it opens from then on
palbase transit:retire <key>
webhooks
palbase webhooks:list
The environment's webhook endpoints: where its events go, which (never their secrets)
palbase webhooks:list
palbase webhooks:create
Add a webhook endpoint; its signing secret is printed this once, or kept in a secret with --secret-to
palbase webhooks:create <url> [--events <a,b>] [--secret-to <NAME> [--dev]]
A machine is given its own endpoint and its secret (PALBASE_WEBHOOK_SECRET) by the platform, for the events its app takes: this is for another receiver. --secret-to keeps the secret in the environment's secrets (production; --dev: development) and prints it nowhere.
--events <list> the events it is sent, comma-separated (user.deleted, …); none: every event
--secret-to <NAME> keep its signing secret in the environment's secret NAME instead of printing it
--dev with --secret-to: the development secrets, not production
palbase webhooks:update
Change a webhook endpoint's address or events (its secret stays)
palbase webhooks:update <id> [--url <url>] [--events <a,b>|""]
--url <url> where it is delivered now
--events <list> the events it is sent now, comma-separated; "" for every event
palbase webhooks:delete
Remove a webhook endpoint: its events are no longer delivered there
palbase webhooks:delete <id>
palbase webhooks:deliveries:list
The deliveries the platform gave up on after its tries, still owed (--failed), or every one kept 30 days (--all)
palbase webhooks:deliveries:list [--failed] [--all]
--failed those it gave up on and still owes (the default)
--all every one kept, those delivered again among them
palbase webhooks:deliveries:redeliver
Send a failed delivery again: the same event, its id and body, to the endpoint it failed at, within seconds
palbase webhooks:deliveries:redeliver <id>
As Stripe's Resend: the receiver takes an event more than once by its Palbase-Event-Id. An admin's; it is written to the organization's audit log.
ai
palbase ai:usage
The environment's uses of models, newest first: what each was, what it cost, and whose key it went out with
palbase ai:usage [--kind text|embedding|image] [--metadata key=value]… [--limit <n>] [--cursor <cursor>]
--kind <kind> only uses of this kind: text, embedding or image
--metadata <key=value> only uses carrying this label (repeat for several): a product's spend, a task's
--limit <n> at most this many (1–500, default 50)
--cursor <cursor> the next page, as the last one said it
palbase ai:spend
What Palbase's models cost the environment this month: the free dollar and what is left of it, the monthly budget, and whether they serve it now — and if not, why
palbase ai:spend
Palbase's models are the platform's provider keys. A month brings a dollar of them free: a Free environment's own, a paying account's once for all its projects. Past it a Free environment uses its own provider keys (ai:providers:set); a Pay as you go one pays from the account's balance, at the provider's price and 10%. Its own provider keys always serve it, and are not counted here: their provider bills them.
palbase ai:models
The models the environment may name — every text model with its rungs, window and price, and the embedding and picture models
palbase ai:models
palbase ai:providers:list
The providers the environment calls with its own key (never the keys): its calls go out with them instead of the platform's
palbase ai:providers:list
palbase ai:providers:set
Call a provider with the environment's own key: the key is piped in or typed unseen, never shown or put on the command line
palbase ai:providers:set <provider> [--key-stdin] < key.txt
Without --key-stdin the key is asked for at the terminal, and not echoed as it is typed or pasted.
--key-stdin read the key from stdin (a pipe, a file)
palbase ai:providers:remove
Forget the environment's own key for a provider: its calls go out with the platform's again
palbase ai:providers:remove <provider>
palbase ai:settings:get
What the environment allows its chats — whether the app's users talk to the agents, how often, how much history is kept — and its monthly AI budget
palbase ai:settings:get
palbase ai:settings:set
Change what the environment allows its chats, and its monthly AI budget: a setting left out is left as it is
palbase ai:settings:set [--clients on|off] [--messages-per-minute <n>] [--history-limit <bytes>|none] [--monthly-budget <usd>|none]
--monthly-budget is the most the environment spends a month on Palbase's models (the platform's provider keys), at what it is billed: past it a call they would serve is refused (ai_budget_reached) until the month turns (UTC) or the budget is raised, and the organization's owners are emailed once. Its own provider keys are not counted: their provider bills them. palbase ai:spend says what is spent. Only an owner or an admin of the organization sets it, as themselves: a member's change of it is refused, and so is a key's or a token's — what spends the money does not raise its own limit.
--clients <on|off> the app's users talk to the agents of their chats with their own session
--messages-per-minute <n> how many messages one user sends a minute (1–600)
--history-limit <bytes|none> the chat history kept at most, in bytes; none: the plan's
--monthly-budget <usd|none> the most spent a month on Palbase's models, in US dollars (0.01–1000000, cents at most); none: no budget
palbase ai:agents:list
The environment's agents: its backend's (its build's contract names them) and those it keeps, a draft and published versions
palbase ai:agents:list
palbase ai:agents:show
One agent: where it stands and what it is — a kept one's published version (or its draft, or a version), a backend's last turn
palbase ai:agents:show <name> [--draft | --version <n>]
--draft the draft, as the next publish makes a version of it
--version <n> that version, as it was published
palbase ai:agents:create
Save the draft of an agent the environment keeps — a new one's, or a change of one's — and publish it with --publish
palbase ai:agents:create <name> [--from <file.json>] [--instructions <text>] [--description <text>] [--model <model>] [--tool <source:name>]… [--output-schema <file.json>] [--revision <n>] [--publish]
Its chats talk to the version published: a draft changes nothing until it is published (ai:agents:publish, or --publish here).
A change of an agent it keeps is made on its draft: each flag given replaces its field, and the rest stays.
--from is the whole definition as JSON ({ "instructions", "model", "tools": [{ "source", "name" }], "subagents", "output", … }) — or what ai:agents:generate --json printed; each flag given replaces its field.
--tool is source:name: backend:<ai.tool> (backend:<machine>/<ai.tool> for another machine's), client:<ai.clientTool>, https:<endpoint>, mcp:<server>, platform:web.search|web.fetch|workspace.fetch_archive (palbase ai:tools lists them). Given, the tools are those; a backend or client tool goes by its name alone, and the platform takes what it does from the build that declares it.
--revision is the draft's revision as it was read (ai:agents:show): when another was saved since, the save is refused (draft_changed) and nothing is lost.
--from <file.json> the definition, as JSON
--instructions <text> what the agent is told to do
--description <text> what it is for, in a sentence or two
--model <model> a role (fast, default, strong) or a model of ai:models
--tool <source:name> a tool it is given (repeat for several)
--output-schema <file.json> the JSON Schema of its one-shot answers
--revision <n> the draft's revision the change is made on
--publish publish the draft as the next version once it is saved
palbase ai:agents:publish
Make the draft of an agent the next version, the one its chats talk to from their next turn
palbase ai:agents:publish <name> [--revision <n>]
--revision <n> the draft as it was read: refused when another was saved since
palbase ai:agents:rollback
Make a version an agent had the one its chats talk to again — and its draft that version
palbase ai:agents:rollback <name> <version>
palbase ai:agents:versions
The versions an agent the environment keeps was published as, newest first, and the one its chats talk to
palbase ai:agents:versions <name> [--limit <n>] [--cursor <cursor>]
--limit <n> at most this many (1–100, default 20)
--cursor <cursor> the next page, as the last one said it
palbase ai:agents:generate
Write an agent from a description — instructions, a model, the environment's tools that fit — and save it as a draft with --save
palbase ai:agents:generate "<description>" [--name <name>] [--save] [--publish]
Nothing is kept unless --save (or --publish) says so: the definition is printed, for ai:agents:create --from. It is written as you — your sign-in, or PALBASE_TOKEN — 20 an hour; the platform pays for it.
--name <name> the name it is to have (left out: one is suggested)
--save save it as the draft of an agent of that name
--publish save it and publish it as the next version
palbase ai:agents:remove
Forget an agent the environment keeps, its versions with it: its chats stay, and are refused a message until one of its name is there again
palbase ai:agents:remove <name>
palbase ai:tools
The tools an agent may be given: the backend's (its build's contract), the environment's endpoints, and the platform's
palbase ai:tools
palbase ai:chats:list
The environment's chats, newest first — every one, or a member's
palbase ai:chats:list [--member <user id>] [--limit <n>] [--cursor <cursor>]
--member <user id> only the chats this user is in
--limit <n> at most this many (1–100, default 100)
--cursor <cursor> the next page, as the last one said it
palbase ai:chats:show
One chat: its agent and members, and what they wrote, newest last — with what each turn cost (--cost)
palbase ai:chats:show <chat id> [--limit <n>] [--cost]
--limit <n> its last this many messages (1–100, default 50)
--cost what each turn a message started cost, its subagents counted
palbase ai:chats:remove
Delete a chat: a turn still running is stopped, and its history no longer counts
palbase ai:chats:remove <chat id>
palbase ai:endpoints:list
The environment's HTTPS tools and MCP servers an agent may be given by name (their signing secrets are never said again)
palbase ai:endpoints:list
palbase ai:endpoints:remove
Forget an HTTPS tool or an MCP server: an agent that names it is given it no more
palbase ai:endpoints:remove <name>
palbase ai:repos:list
The repositories a chat's workspace works in, by name, and the machine that gives their tokens
palbase ai:repos:list [--limit <n>] [--cursor <cursor>]
--limit <n> at most this many (1–1000, default 100)
--cursor <cursor> the next page, as the last one said it
palbase ai:repos:remove
Forget a repository: a chat's workspace checks it out no more
palbase ai:repos:remove <name>
support
palbase support:ask
Ask Palbase's support line about Palbase: answered from its documentation and what this environment shows; what Palbase lacks is logged, and you are mailed when Palbase closes it
palbase support:ask "<question>" [--no-context]
It is answered by a model Palbase pays for, from Palbase's documentation and — unless --no-context — this environment's machines, their last builds and a failed build's log, read with your sign-in or PALBASE_TOKEN (what it cannot read is left out). Keys, tokens and passwords in what is sent are masked before anything reads them. When the answer finds something Palbase lacks, it is logged under the case it names, and you are mailed when it is closed (asked with an environment token, the owners of its organization are). A person asks 30 questions an hour, a project's people 60. An environment token narrowed to the support scope asks too.
--no-context send the question alone, nothing of this environment's machines
docs
palbase docs:search
Search Palbase's documentation by meaning: the sections that answer it, each with its address and its text
palbase docs:search "<what you need>" [--limit <n>]
Ask in English, the docs' language: a question finds the section it means by its meaning and by its words — say what you need, not the words of a heading. Each result is a section of a page: its title, its address with the section's anchor, and its text (the first 4000 characters of a longer one — the address has the rest). Anyone may search, signed in or not: 10 searches in ten seconds, 30 a minute, 300 an hour. A sign-in or PALBASE_TOKEN is sent when there is one and is counted as itself; with none, your address is. When many search at once, a search waits its turn for a few seconds, or is told to search again in a moment.
--limit <n> how many sections (1 to 10, default 5)
stores
palbase stores:apple
The environment's Apple account and Apple ID: whether each is connected, what Apple last said of them, what the key reaches
palbase stores:apple
palbase stores:apple:connect
Connect an App Store Connect API key, or sign an Apple ID in: either goes from this computer straight to the environment, never through Studio
palbase stores:apple:connect --key-file <AuthKey_KEYID.p8> --issuer-id <issuer id> [--key-id <KEYID>] | --apple-id <email> [--team <team>] | [--apple-id <email>] --print-link
App Store Connect → Users and Access → Integrations → App Store Connect API makes the key and shows the Issuer ID; stores:apple then says what its role reaches. Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead. --apple-id signs an Apple ID in, for what Apple's API does not do: App Review's messages and the answer to them, App Privacy, making an app record. Its password is read here and handed to Apple through the environment, never kept; then Apple's code, and the team when the Apple ID is in more than one. An owner's or an admin's.
--apple-id <email> sign this Apple ID in instead of connecting a key
--team <team> the Apple ID's team, by name or id, when it is in more than one
--key-file <AuthKey_KEYID.p8> the .p8 file Apple gave when the key was made
--key-id <KEYID> the key's id (default: from the file's name)
--issuer-id <issuer id> the account's issuer id
--print-link only print a link that takes the key in a browser (valid 30 minutes, once)
palbase stores:apple:check
Ask Apple about the connected key again (after the account holder accepted its agreements, or its role changed), or with --apple-id about the Apple ID
palbase stores:apple:check
--apple-id check the Apple ID's session instead: whether Apple still takes it, its team and roles
palbase stores:apple:disconnect
Take the environment's Apple key away (Apple keeps its apps and builds), or with --apple-id sign its Apple ID out
palbase stores:apple:disconnect
--apple-id sign the Apple ID out, at Apple too, and forget its session; the key stays
palbase stores:apple:keys
The keys Palbase made at Apple for the environment with its Apple ID, where each stands, and which keys the environment lacks
palbase stores:apple:keys
Palbase makes them with the Apple ID signed in for the environment (stores:apple:connect --apple-id): stores:apple:keys:make. No one downloads a .p8.
palbase stores:apple:keys:make
Make at Apple, with the environment's Apple ID, the keys it lacks — App Store Connect API, APNs, In-App Purchase — and connect them: one confirmation
palbase stores:apple:keys:make
With no flag, every key the environment lacks; a key it has is kept. The Apple ID is an Admin or the Account Holder of the team. APNs: one key for production and one for the sandbox, scoped to the environment's iOS apps; notify takes them. In-App Purchase: for one iOS app (--bundle with more than one); purchases connects it.
--asc the App Store Connect API key
--apns the APNs keys
--iap the In-App Purchase key
--bundle <value> the iOS app the In-App Purchase key is for, when the environment has more than one
palbase stores:apple:keys:revoke
Revoke at Apple an APNs or In-App Purchase key Palbase made for the environment (the App Store Connect key is revoked as stores:apple:disconnect takes it away)
palbase stores:apple:keys:revoke <key-id>
palbase stores:apple:share
Share the environment's Apple ID session with another environment of the project, which uses it while it has none of its own
palbase stores:apple:share <environment>
An environment of the same project alone: Palbase refuses another project's, of your organization or not (other_project). You are an owner or an admin of both environments; a key or an environment token does not share. Signed out, or signed in again, the session serves none it was shared with: share it again. stores:apple:unshare takes it back.
palbase stores:apple:unshare
Take the environment's Apple ID session back from another environment of the project
palbase stores:apple:unshare <environment>
palbase stores:google
The environment's Play connection: the service account Play Console invites, what Play let it reach
palbase stores:google
palbase stores:google:connect
Connect Google Play: Palbase makes a service account for the environment, which your Play Console invites
palbase stores:google:connect
No key changes hands: Palbase acts as the service account it made in its own Google Cloud project. Then, in Play Console → Users and permissions, invite the service account with the app (Release to testing tracks; for purchases also View financial data and Manage orders and subscriptions), link the Android app (stores:apps:link) and check: stores:google:check.
palbase stores:google:check
Ask Play what the environment's service account reaches of each Android app: after Play Console invited it
palbase stores:google:check
palbase stores:google:disconnect
Delete the environment's service account at Google: Play Console's invite of it reaches nothing any more
palbase stores:google:disconnect
palbase stores:apple:sandbox-testers
The Apple team's sandbox testers: the accounts that buy in the App Store's sandbox, how fast their subscriptions renew
palbase stores:apple:sandbox-testers
They are the team's, every environment's alike. Only a person makes one: App Store Connect › Users and Access › Sandbox.
palbase stores:apple:sandbox-testers:set
Change how a sandbox tester buys: how fast its monthly subscriptions renew, whether its purchases are interrupted, its storefront
palbase stores:apple:sandbox-testers:set <email|id> [--renewal 3m|5m|15m|30m|1h] [--interrupt on|off] [--storefront <code>]
A monthly subscription renews at the rate given; a yearly one in proportion. Apple renews one 12 times at most, then lets it end. An interrupted purchase stops for an action of the buyer's, as when a bank asks for one: what the app does then is tested.
--renewal <rate> every 3m, 5m, 15m, 30m or 1h
--interrupt <on|off> interrupt its purchases
--storefront <code> the storefront it buys in, three letters (USA, DEU, TUR …)
palbase stores:apple:sandbox-testers:clear
Clear sandbox testers' purchase history: each buys again as for the first time, introductory offers included
palbase stores:apple:sandbox-testers:clear <email|id>…
Apple clears them after it answers, which can take minutes. Then sign out of the sandbox account on the device and in again.
palbase stores:apps
The environment's apps linked to their store
palbase stores:apps
palbase stores:apps:link
Link an app of the project to its store: an iOS app to the App Store, an Android app to Google Play (with an upload key Palbase makes)
palbase stores:apps:link <app id> [--name <name in the store>]
--name <name> its name in the store (default: the bundle id or package)
palbase stores:apps:record
Make a linked iOS app's App Store Connect record with the Apple ID — Apple's API leaves it to a person on the website — and link the app to it
palbase stores:apps:record <app id> --name <App Store name> [--sku <sku>] [--locale <en-US>] [--company <seller name>]
--name <name> its App Store name (30 characters at most)
--sku <sku> the record's own id in the team (default: the bundle id)
--locale <locale> its store page's first language (default: en-US)
--company <name> the seller's name, which a team's first app sets
palbase stores:apps:record:remove
Remove a linked iOS app's App Store Connect record with the Apple ID, as App Store Connect's Remove App does (an app never approved)
palbase stores:apps:record:remove <app id>
palbase stores:apps:upload-key
An Android app's upload key: the certificate Palbase signs its builds with (never its private key)
palbase stores:apps:upload-key <app id> [--out <file.pem>]
Play Console shows an app's upload key by this fingerprint; asked to change an app's upload key to Palbase's, it takes this certificate (--out).
--out <file.pem> write the certificate (PEM) to this file
palbase stores:feedback
An app's TestFlight feedback: testers' screenshots and crashes
palbase stores:feedback <app id>
palbase stores:feedback:get
One of an app's TestFlight feedback: the device, the build, and a crash's log
palbase stores:feedback:get <app id> <crash|screenshot> <feedback id>
A crash's log is cut at 32 KiB: the exception and the crashed thread come first. A screenshot's images are Apple's links, which expire.
palbase stores:webhook
An iOS app's App Store Connect webhook: whether Apple tells Palbase each change at once, and its last delivery
palbase stores:webhook <app id> [--make] [--ping]
Palbase makes the webhook when the app is linked (the key's role must be Admin or App Manager), and tries again every day while it is not on; meanwhile it reads Apple every few minutes. --make points it at the environment again with a new secret; --ping asks Apple to send it a ping and waits for it to arrive.
--make make it again: a new secret, pointed at the environment
--ping ask Apple to send it a ping, and wait for it to arrive
palbase stores:vitals
An app's health as its store has it: crashes, terminations and hangs, crash and ANR rates by version
palbase stores:vitals <app id>
An iOS app: the crashes testers sent from TestFlight, what TestFlight counted of its newest builds (installs, sessions, crashes, feedback), its App Store versions' terminations, hangs and launches, and App Store crashes by day (the App Crashes report, an Admin key's). An Android app: Play's Android vitals — each version code's crash and ANR rates over seven days, its errors, and what Play flagged. A new day of it is the environment's stores.vitals.changed event.
palbase stores:packages
The packages handed to the stores, newest first
palbase stores:packages [--app <app id>] [--limit <n>]
--app <app id> only this app's
--limit <n> how many (1–200, default 20)
palbase stores:packages:get
One package: its build, its bundles and its deliveries
palbase stores:packages:get <package id>
palbase stores:packages:create
Make a package of an app: its build number, and the URL and token its .ipa is uploaded with
palbase stores:packages:create <app id> --command-id <id> [--notes <text>] [--entitlements-file <json>]
The build is made with the build number it answers (CFBundleVersion), then uploaded: palbase stores:packages:upload. The same --command-id again answers the same package, with a new upload token while it waits for its build: a build machine sends its run's id.
--command-id <id> names the request (a CI run id): sent again, the same package
--notes <text> notes kept with it (a commit, a branch)
--entitlements-file <json> what each bundle asks for, by bundle id ({"com.acme.app": {"aps-environment": "production"}})
palbase stores:packages:upload
Hand a package its build: the .ipa or .aab goes straight to the environment's address with the upload token
palbase stores:packages:upload <package id> --file <app.ipa|app.aab> --token <upload token>
The token is the one stores:packages:create printed; PALBASE_UPLOAD_TOKEN stands in for --token, out of the process list. The file goes with its Content-Length. While the module is busy with another upload it is sent again, after the wait the module names, five times at most; a package that has its build already (this token's upload arrived before) counts as uploaded.
--file <app.ipa|app.aab> the unsigned .ipa, or the Android App Bundle (unsigned, or signed with the app's upload key), the build machine made
--token <upload token> the package's upload token
palbase stores:packages:bundle
Download a package's bundle as Palbase signed it for Play: to upload it in Play Console by hand
palbase stores:packages:bundle <package id> --out <file.aab> [--channel play_internal]
stores:deliver sends every build to Play, an app's first too; when Play wants a release made in Play Console instead (Testing → Internal testing → Create new release), upload this file there: it is signed with the app's upload key. The bytes come from the environment's address on a link the module sealed for this bundle, valid 15 minutes; Studio is asked for the link alone.
--out <file.aab> where to write it
--channel <channel> the Play delivery (play_internal)
palbase stores:deliver
Send a package to a channel's testers: signed, handed to Apple or Play, and given to the testers
palbase stores:deliver <package id> [--channel testflight|play_internal] [--groups <group,…>] [--what-to-test <text>] [--locale <locale>]
An iOS app goes to TestFlight: processed, then given to the groups; an export compliance question is answered with stores:export-compliance.
An Android app goes to Play's internal testing track, to the testers it lists in Play Console. An invite given in Play Console just now takes a while to reach Play's API: a delivery Play refuses then (google_not_allowed) is sent again later.
palbase stores:deliveries <package id> follows it.
--channel <channel> where it goes: testflight, or play_internal (default: the app's platform's)
--groups <group,…> the groups it goes to once ready, by id or name
--what-to-test <text> the note testers read
--locale <locale> the note's locale (default: the app's primary one)
palbase stores:deliveries
A package's deliveries: where each is now, and what to do when one failed
palbase stores:deliveries <package id>
palbase stores:what-to-test:set
Change what testers read with a package's TestFlight build, once Apple holds the build
palbase stores:what-to-test:set <package id> --text <text> [--locale <locale>]
Apple's note on the build, and the delivery's own; before Apple holds the build the module says so (build_not_at_apple) — stores:deliver --what-to-test sets it with the delivery.
--text <text> the note testers read (4000 characters at most)
--locale <locale> its locale (default: the app's primary one)
palbase stores:groups
An app's groups of testers
palbase stores:groups <app id>
palbase stores:groups:create
Make a group of testers for an app (in TestFlight an internal and an external group)
palbase stores:groups:create <app id> <name>
palbase stores:groups:rename
Rename a group
palbase stores:groups:rename <group id> <name>
palbase stores:groups:delete
Delete a group: its testers lose the builds it gave them
palbase stores:groups:delete <group id>
palbase stores:groups:testers
A group's testers
palbase stores:groups:testers <group id>
palbase stores:groups:testers:set
Set who a group holds, whole: those not named leave it
palbase stores:groups:testers:set <group id> (--emails <a@x,…> | --file <testers.txt>)
A file names one tester a line — email, or email,First,Last — or is a JSON array; - reads it from stdin. 100 at most.
--emails <a@x,…> the testers' addresses
--file <path> the testers, one a line (- for stdin)
palbase stores:groups:add-build
Give a group a package that TestFlight has (external testers get it after Beta App Review)
palbase stores:groups:add-build <group id> <package id>
palbase stores:review-info
An app's TestFlight test information as Apple holds it, in one locale, and what Beta App Review still lacks — the demo password only as set or not
palbase stores:review-info <app id> [--locale <locale>]
--locale <locale> the locale to read (default: the app's primary one)
palbase stores:review-info:set
What Beta App Review needs to know of an app: only the fields given are written; it answers what is still missing
palbase stores:review-info:set <app id> [--description <text>] [--feedback-email <a@x>] [--contact-… <…>] [--demo-account-name <name> --demo-password-stdin] [--notes <text>] [--locale <locale>]
--description <text> what testers should test
--feedback-email <a@x> where testers send feedback
--contact-first-name <name> whom Apple calls: first name
--contact-last-name <name> last name
--contact-phone <phone> phone
--contact-email <a@x> email
--demo-account-name <name> a sign-in for the reviewer, when the app needs one
--demo-password-stdin read the demo account's password from stdin (never the command line)
--notes <text> notes to the reviewer
--locale <locale> the description's and feedback address's locale (default: the app's primary one)
palbase stores:beta-review
Plan sending a package to Beta App Review (external testers get it after Apple's review); it runs when confirmed
palbase stores:beta-review <package id>
palbase stores:export-compliance
Plan the export compliance answer of a package's build (the question TestFlight waits on); it runs when confirmed
palbase stores:export-compliance <package id> --uses-non-exempt-encryption yes|no
--uses-non-exempt-encryption <yes|no> whether the app uses encryption beyond HTTPS and what iOS provides
palbase stores:expire
Plan expiring a package's build in TestFlight (testers can no longer install it); it runs when confirmed
palbase stores:expire <package id>
palbase stores:listing:get
An iOS app's draft App Store page — categories, age rating, each language's text and screenshots — and its revision
palbase stores:listing:get <app id> [--out <palbase/store/listing.json>]
--out writes the draft to a file to edit, under palbase/; stores:listing:put saves the edit on the revision printed. A screenshot can be named there by its file under palbase/ (relative to the draft's file) instead of its SHA-256: listing:put uploads it first. Nothing of the draft is at Apple: stores:listing:plan compares it with App Store Connect, and the plan, once confirmed, writes it.
--out <file> write the draft to this file, under palbase/
palbase stores:listing:put
Save an iOS app's draft App Store page, whole, on the revision it was edited from — its screenshot files uploaded first
palbase stores:listing:put <app id> --file <palbase/store/listing.json> --revision <n>
A screenshot is named by its SHA-256, or by a file — a path from the draft's own file, under palbase/ (nothing else is read): each file is uploaded first, and the draft is saved with its SHA-256 in its place. The file itself is left as it is. Of two people editing at once, the second is told — the draft moved past the revision they edited — never saved over. Nothing goes to Apple: stores:listing:plan says what the draft changes there.
--file <listing.json> the draft, as stores:listing:get --out wrote it ('-' or none: stdin)
--revision <n> the revision it was edited from, as stores:listing:get printed it (0: the first)
palbase stores:screenshots:upload
Upload screenshots for an iOS app's App Store page: each is kept by its SHA-256, which the draft names it by
palbase stores:screenshots:upload <app id> <file.png|file.jpg>…
Each is a PNG or JPEG of 10 MB at most, of a size the App Store takes: iPhone 6.9" (1320x2868, 1290x2796, 1260x2736), iPhone 6.5" (1242x2688, 1284x2778) or iPad 13" (2064x2752, 2048x2732), or turned. The same image again is the one kept. stores:listing:put uploads the files a draft names itself; this is for the SHA-256 alone.
palbase stores:review-screenshot
Upload the screenshot App Review is shown of an in-app purchase: kept by its SHA-256, which the catalog names it by
palbase stores:review-screenshot <app id> <file.png|file.jpg>
A PNG or JPEG of 10 MB at most, at an App Store screenshot size — iPhone 6.9" 1320x2868, 1290x2796 or 1260x2736; iPhone 6.5" 1242x2688 or 1284x2778; iPad 13" 2064x2752 or 2048x2732 (or turned): App Review takes an in-app purchase's screenshot at no other.
Name it in the catalog, channels.ios.review.screenshot = "sha256:<hex>"; the next plan (palbase purchases:catalog:plan) gives it to App Store Connect, and Apple then has what it needs of the product to review it.
palbase stores:listing:plan
Plan writing an iOS app's draft page to App Store Connect: what changes, language by language; it runs when confirmed
palbase stores:listing:plan <app id>
The draft is compared with the page of the app's version that can change; a field the draft leaves empty, and a language it has not, are left as they are.
palbase stores:version
An iOS app's App Store version as App Review will see it — its build, release, phased release, price, content rights, review contact — and what App Review would refuse it for
palbase stores:version <app id>
The demo account's password is never read out: only whether Apple holds one. watched is what the module last saw of the version sent to App Review (it reads it every few minutes).
palbase stores:version:prepare
Prepare an iOS app's App Store version: its number, build, copyright, release, phased release, content rights, a free price, App Review's details
palbase stores:version:prepare <app id> [--version <x.y.z>] [--build <n>] [--copyright <text>] [--release manual|after_approval|scheduled] [--release-at <RFC 3339>] [--phased | --no-phased] [--third-party-content | --no-third-party-content] [--free] [--territories all] [--app-privacy published|unpublished] [--review-file <review.json>] [--demo-password-stdin]
Only what is given is written; --version makes the next version when the app has none that can change. The build is a TestFlight build of that version, by its number. --review-file holds App Review's details as stores:version --json shows them (its review): a field the file leaves out stays as it is. The demo account's password is read from stdin alone (--demo-password-stdin), never from the file or the command line; it goes to Apple, and is never printed or kept.
--version <x.y.z> the version's number: makes it when the app has none that can change
--build <n> the TestFlight build App Review gets, by its build number
--copyright <text> the copyright line (2026 Your Company)
--release <how> how it goes out once approved: manual, after_approval or scheduled
--release-at <RFC 3339> a scheduled release, not before then (alone, it is scheduled)
--phased release it in phases over seven days
--no-phased release it to everyone at once
--third-party-content the content rights statement: the app shows, contains or reaches third-party content
--no-third-party-content the content rights statement: it does not
--free make an app with no price free everywhere (a paid price is set in App Store Connect)
--territories <all> all: sell an app sold nowhere in every country and region (chosen ones are set in App Store Connect)
--app-privacy <published|unpublished> say the app's privacy details were published in App Store Connect (Apple has no API for them): the missing list stops asking for them
--review-file <review.json> App Review's details: contact, demo account, notes ('-': stdin)
--demo-password-stdin read the demo account's password from stdin (never a file or the command line)
palbase stores:version:submit
Plan sending an iOS app's prepared version to App Review — refused, with what is missing, until it has what App Review needs; it runs when confirmed
palbase stores:version:submit <app id> [--with <product id>,…]
--with sends the app's in-app purchases and subscriptions with the version, by product id: a product's first review goes with an app version, as Apple requires, and each is READY_TO_SUBMIT at Apple (its text, price, availability and review screenshot complete) or nothing is planned.
--with <product id,…> in-app purchases sent to App Review with the version, by product id
palbase stores:version:withdraw
Plan taking the version out of App Review: it is edited again, and sent again from the start; it runs when confirmed
palbase stores:version:withdraw <app id>
palbase stores:version:release
Plan releasing an approved version that waits for its developer (a manual release) on the App Store, for good; it runs when confirmed
palbase stores:version:release <app id>
palbase stores:version:phased
Plan pausing, resuming or completing the version's phased release (complete: everyone gets it now); it runs when confirmed
palbase stores:version:phased <app id> <paused|active|complete>
palbase stores:app-review
An iOS app's Resolution Center, read with the Apple ID: App Review's messages, the guidelines a rejection names, its attachments, the answer not sent yet
palbase stores:app-review <app id>
Apple's API gives no reason for a rejection: the Apple ID signed in for the environment reads it (stores:apple:connect --apple-id). The five threads written to last.
palbase stores:app-review:answer
Write the answer to an App Review thread, as App Store Connect's draft; --send sends it
palbase stores:app-review:answer <app id> <thread id> [--file <answer.txt>] [--send]
The answer is read from --file, or piped in. Without --send it stays a draft, which App Store Connect's website shows too; with --send it goes to App Review.
--file <answer.txt> the answer (default: stdin)
--send send it to App Review once written
palbase stores:privacy
An iOS app's App Privacy details as App Store Connect has them, read with the Apple ID
palbase stores:privacy <app id>
palbase stores:privacy:set
Write an iOS app's App Privacy details at Apple with the Apple ID, and publish them with --publish
palbase stores:privacy:set <app id> --file <usages.json> [--publish]
The file is the details' lines in Apple's words: [{"category":"USER_ID","purpose":"APP_FUNCTIONALITY","data_protection":"DATA_LINKED_TO_YOU"}], or [{"data_protection":"DATA_NOT_COLLECTED"}] alone. What Apple has and the file lacks is removed; what the file has and Apple lacks is made.
--file <usages.json> the lines (default: stdin)
--publish publish them once written
palbase stores:actions
The actions planned at Apple, newest first: what each does, and whether it ran (running: a page still being written)
palbase stores:actions [--status planned|done|declined|failed] [--limit <n>]
--status <status> only those planned, done, declined or failed
--limit <n> how many (1–200, default 30)
palbase stores:actions:confirm
Run a planned action at Apple: it names the hash of the plan that was shown, and is asked about first
palbase stores:actions:confirm <action id> --hash <hash>
What it does is shown, then asked about — in every environment, as it cannot be undone at Apple. -y confirms without asking. An App Store page's plan is written after the answer (its screenshots take minutes): the command waits for it to be done or failed, 15 minutes at most; stopping the wait does not stop the writing.
--hash <hash> the plan's hash, as it was shown when it was planned
palbase stores:actions:decline
Decline a planned action: it never runs
palbase stores:actions:decline <action id>
purchases
palbase purchases:catalog:get
The environment's catalog: its newest revision, the products it sells and what they give
palbase purchases:catalog:get [--out <catalog.json>]
--out writes the catalog to a file to edit; catalog:put saves the edit with the revision it was edited from, which is printed.
--out <catalog.json> write the catalog (the JSON catalog:put takes) to this file
palbase purchases:catalog:put
Save a new revision of the whole catalog, on the revision it was edited from; nothing is written to a store
palbase purchases:catalog:put --file <catalog.json> --revision <n>
The module checks it first: a catalog that breaks a rule is refused with every problem, and nothing is saved. Of two people editing at once, the second is told — the catalog moved past the revision they edited — never overwritten. The stores are brought to it by a plan: palbase purchases:catalog:plan.
--file <catalog.json> the catalog, as catalog:get --out wrote it ('-' or none: stdin)
--revision <n> the revision it was edited from (0 for the first), as catalog:get printed it
palbase purchases:catalog:validate
Check a catalog without saving it: every problem, with its path and the store whose rule it breaks
palbase purchases:catalog:validate --file <catalog.json>
--file <catalog.json> the catalog ('-' or none: stdin)
palbase purchases:catalog:plan
Plan bringing the stores to the catalog's newest revision, store by store, before anything is written
palbase purchases:catalog:plan [<plan id>]
A plan is confirmed with its Stripe part's hash (purchases:catalog:confirm) — with no Stripe account connected, its App Store part's; the App Store and Google Play parts are written by the stores module once their accounts are connected. With a plan's id it makes none: it shows that plan, and once confirmed what each store answered — the state of every product written, and a price that changes on a later day.
palbase purchases:catalog:confirm
Write a plan — its Stripe part now, its App Store part through the stores module: it names the hash of the plan that was shown, and is asked about first
palbase purchases:catalog:confirm <plan id> --hash <hash>
What it writes is shown, then asked about — in every environment: a price written to Stripe is never changed or removed there, a product made in App Store Connect never goes away. -y confirms without asking.
--hash <hash> the hash the plan printed (its Stripe part's; with no Stripe account, its App Store part's)
palbase purchases:catalog:decline
Decline a plan: its Stripe part is never written
palbase purchases:catalog:decline <plan id>
palbase purchases:catalog:revisions
Every revision the catalog was saved as, the newest first: who saved it, when, and when a plan of it was written to the stores
palbase purchases:catalog:revisions [--limit <n>] [--cursor <next>]
purchases:catalog:revision <n> reads one; purchases:catalog:restore <n> puts it back as the next revision.
--limit <n> revisions a page (default 50, at most 200)
--cursor <next> the next page, as the last one printed it
palbase purchases:catalog:revision
The catalog as it was saved at a revision
palbase purchases:catalog:revision <revision> [--out <catalog.json>]
--out writes that revision's catalog to a file, as catalog:get --out writes the newest.
--out <catalog.json> write the revision's catalog to this file
palbase purchases:catalog:restore
Put an older revision of the catalog back, as its next revision; nothing is written to a store
palbase purchases:catalog:restore <revision>
It is saved as an edit is: checked against today's rules, what customers bought kept in it — a revision that breaks one is refused with every problem. The stores are brought to it by a plan, confirmed: palbase purchases:catalog:plan.
palbase purchases:customers
Find customers by what a person writing in has: their id, an id their store gave a purchase, or the email they bought with on the web
palbase purchases:customers <query>
The query is a customer's id; Apple's original transaction id or any transaction id; a Google order id (GPA.…, a renewal's ..N or not); Stripe's subscription, payment intent, invoice or customer id; or an email address. An email finds a buyer who had no account at checkout, matched whole (case aside): the address is never kept, an HMAC of it is.
palbase purchases:customer:get
What a customer has, whatever store they bought in: entitlements, subscriptions, purchases, credits and limits
palbase purchases:customer:get <customer id>
The customer is the user's id; a guest merged into an account answers as the account.
palbase purchases:customer:history
What happened to a customer, the newest first: payments, refunds, cancellations, grants, credits, moves — with who did each, and why
palbase purchases:customer:history <customer id> [--limit <n>] [--cursor <next>]
Credits spent are the credits' own entries; here are the credits given, taken back, expired and forgiven.
--limit <n> events a page (default 50, at most 200)
--cursor <next> the next page, as the last one printed it
palbase purchases:customer:grants
The entitlements given a customer — active, ended and revoked — with each grant's id, reason and who gave it
palbase purchases:customer:grants <customer id>
End one: palbase purchases:customer:revoke <customer id> <grant id>.
palbase purchases:customer:grant
Give a customer one of the catalog's entitlements, for a while or for life, saying why — a promotion, a support case, a tester
palbase purchases:customer:grant <customer id> <entitlement> --reason <why> [--for 30d | --until <date>]
The reason is the record. Without --for or --until the grant is for life. It prints the grant's id: purchases:customer:revoke ends it. A customer not seen before is made; nothing is charged, in any store.
--reason <why> why it is given (500 characters at most): the record
--for <span> how long: 12h, 30d, 2w
--until <date> when it ends (2026-12-31, or a time)
palbase purchases:customer:revoke
End a granted entitlement now; what the customer bought is not touched
palbase purchases:customer:revoke <customer id> <grant id>
The grant's id is the one purchases:customer:grant printed. The entitlement stays on while a purchase still gives it.
palbase purchases:customer:extend
Move a customer's subscription renewal days later, for free: the App Store's extension, Google Play's deferral, Stripe's next charge moved
palbase purchases:customer:extend <customer id> <subscription id> --days <1-90> --reason customer_satisfaction|service_issue|other [--note <why>]
The subscription's id is the one purchases:customer:get prints. The App Store extends a paid, active subscription, at most 90 days and twice a year. The store is asked again after: the new expiry is printed, and the customer's history keeps it with who did it.
--days <n> how many days (1 to 90)
--reason <why> customer_satisfaction, service_issue or other: as the App Store asks it
--note <why> what support writes of it (500 characters at most): the record
palbase purchases:customer:refund
Refund a customer's subscription — its latest payment, or what is left of its period — and end it now: Google Play and Stripe
palbase purchases:customer:refund <customer id> <subscription id> --mode full|prorated [--note <why>]
The customer's access ends as it answers. The App Store decides its own refunds: the customer asks Apple, which asks the app what they used — Palbase answers it when they consented in the app.
--mode <kind> full: the latest payment; prorated: what is left of its period
--note <why> what support writes of it (500 characters at most): the record
palbase purchases:customer:cancel
Turn a customer's subscription renewal off: it ends at its period's end, the customer keeping what they paid for — Google Play and Stripe
palbase purchases:customer:cancel <customer id> <subscription id> [--note <why>]
An App Store subscription's renewal is the customer's to turn off (Settings › Apple ID › Subscriptions).
--note <why> what support writes of it (500 characters at most): the record
palbase purchases:customer:credits
A customer's credit entries, the newest first: given, spent, taken back, expired, forgiven — with who did each, and why
palbase purchases:customer:credits <customer id> [--credits <key>] [--limit <n>] [--cursor <next>]
--credits <key> one of the catalog's credits alone
--limit <n> entries a page (default 50, at most 200)
--cursor <next> the next page, as the last one printed it
palbase purchases:customer:credits:give
Give a customer credits, saying why — a support case, a promotion — for a while or until spent
palbase purchases:customer:credits:give <customer id> <credits> <amount> --reason <why> [--source support|promotion] [--for 30d | --until <date>]
What they owe is paid first from what is given. Nothing is charged, in any store.
--reason <why> why they are given (500 characters at most): the record
--source <kind> support (the default) or promotion
--for <span> how long they last: 12h, 30d, 2w
--until <date> when they expire (2026-12-31, or a time)
palbase purchases:customer:credits:forgive
Forgive what a customer owes of a credit — all of it, or an amount — saying why
palbase purchases:customer:credits:forgive <customer id> <credits> --reason <why> [--amount <n>]
--reason <why> why it is forgiven (500 characters at most): the record
--amount <n> how much of it (default: all they owe)
palbase purchases:customer:limit:set
Set a customer's own maximum of a limit — above or below the catalog's — saying why, for a while or until cleared
palbase purchases:customer:limit:set <customer id> <limit> <max> --reason <why> [--for 30d | --until <date>]
--reason <why> why (500 characters at most): the record
--for <span> how long: 12h, 30d, 2w
--until <date> when it ends (2026-12-31, or a time)
palbase purchases:customer:limit:clear
Clear a customer's own maximum of a limit: the catalog's counts again
palbase purchases:customer:limit:clear <customer id> <limit>
palbase purchases:customer:limit:give
Give a customer more of a limit for its current period, saying why — a gift on top of their maximum
palbase purchases:customer:limit:give <customer id> <limit> <amount> --reason <why> [--source support|promotion]
--reason <why> why it is given (500 characters at most): the record
--source <kind> support (the default) or promotion
palbase purchases:stripe
The environment's Stripe account: whether one is connected, what Stripe last said of its key
palbase purchases:stripe
palbase purchases:stripe:grant
A link that connects a Stripe restricted key: the key goes from the browser straight to the environment, never through Studio or this computer
palbase purchases:stripe:grant
The link works once, for 30 minutes; a project holds 10 open at most.
palbase purchases:stripe:install
A link that connects the environment's Stripe account with one click: Palbase's Stripe App, installed at Stripe — no key pasted
palbase purchases:stripe:install
The link opens Palbase's install page, naming the project; from there Stripe: pick the account, accept what the App may do, and Stripe sends you back to Palbase, which connects it. It works once, for 30 minutes; --live installs the App in live mode (Stripe test mode otherwise). A restricted key connects an account too: palbase purchases:stripe:grant.
--live install the App in live mode (test mode otherwise)
palbase purchases:storekit
An Xcode StoreKit configuration of the environment's iOS products, under palbase/: a build Xcode runs buys them with no App Store
palbase purchases:storekit [--app <app>] [--out <palbase/….storekit>] [--storefront <code>]
Each plan is the App Store product its store id names, with its text in every language the catalog has and its price; a subscription is in its group, at its tier's level, with its introductory offer and the catalog's offers on it as promotional offers (a grant has no store). Prices are in the storefront's currency: the catalog's price, or the amount set by hand in that currency. The storefront is --storefront, else the one the file has, else the one the catalog's currency is the App Store's own price in (USD: USA, EUR: Germany, TRY: Turkey …), else USA. Made again, the file is made whole from the catalog — the same catalog, the same file — keeping the test settings Xcode's Editor menu wrote in it. Nothing outside palbase/ is written: Xcode is pointed at the file in the scheme.
--app <app> the iOS app it is for (default: the one this checkout syncs, or the project's only one)
--out <palbase/….storekit> where under palbase/ (default: palbase/environments/<env>/<app>.storekit)
--storefront <code> the App Store storefront prices are in, by its three letters (USA, DEU, TUR, GBR, JPN …)
palbase purchases:apple
The environment's App Store connection: the In-App Purchase key, where the App Store reports, whether Xcode's purchases are taken
palbase purchases:apple
palbase purchases:apple:connect
Connect an In-App Purchase key for an iOS app: it goes from this computer straight to the environment, never through Studio
palbase purchases:apple:connect --key-file <SubscriptionKey_KEYID.p8> [--key-id <KEYID>] [--issuer-id <issuer id>] [--app <app>] | --print-link
App Store Connect → Users and Access → Integrations → In-App Purchase makes the key (SubscriptionKey_<KEYID>.p8) and shows the Issuer ID — the team's, the same as its App Store Connect API keys': left out, the one stores:apple connected is taken.
The environment verifies the app's purchases and reads its subscriptions with it, and has the stores module point the App Store's notifications at the environment.
Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead.
--key-file <SubscriptionKey_KEYID.p8> the .p8 file Apple gave when the key was made
--key-id <KEYID> the key's id (default: from the file's name)
--issuer-id <issuer id> the team's issuer id (default: the connected App Store Connect key's)
--app <app> the iOS app (default: the one this checkout syncs, or the project's only one)
--print-link only print a link that takes the key in a browser (valid 30 minutes, once)
palbase purchases:apple:disconnect
Forget the environment's In-App Purchase key: the App Store's purchases and notifications are refused from then on (what was bought stays)
palbase purchases:apple:disconnect
palbase purchases:apple:xcode-certificate
Take the purchases Xcode's StoreKit Testing signs with this certificate, as test purchases — never on production; --remove stops
palbase purchases:apple:xcode-certificate <certificate.cer> | --remove
Xcode saves the certificate: select the .storekit file (palbase purchases:storekit makes it), then Editor › Save Public Certificate. From then on what the app reports signed with it — Xcode's own purchases, on a developer's Mac — is a test purchase of the environment, with or without an In-App Purchase key; nothing of it is asked of Apple. Set again, it replaces the one before. The production environment takes only what the App Store signed: it is refused there.
--remove stop taking Xcode's purchases (those taken stay)
palbase purchases:google
The environment's Google Play purchases: the app they are made in, the topic its Play Console names, whether Play's test notification arrived
palbase purchases:google
palbase purchases:google:connect
Sell on Google Play in one of the project's Android apps: Palbase makes the app's notifications topic and has it pushed to this environment
palbase purchases:google:connect [--app <app>]
The project's Play connection (palbase stores:google:connect) is the account Palbase reads and acknowledges the app's purchases as: its Play Console invites it with View financial data and Manage orders and subscriptions. Then, in Play Console: Monetize with Play › Monetization setup › Real-time developer notifications, the topic this prints, Save, and Send test notification — palbase purchases:google says when it arrived.
--app <app> the Android app (default: the one this checkout syncs, or the project's only one)
palbase purchases:google:disconnect
Stop taking the environment's Google Play purchases: Play's notifications stop reaching it, and the app's reports are refused (what was bought stays)
palbase purchases:google:disconnect
palbase purchases:doctor
Whether the environment sells in the App Store now, check by check — the App Store Connect side and the purchases side — with the fix of each that fails
palbase purchases:doctor [--app <app>]
Apple is asked now: the keys, the agreements, the app's App Store Connect record, where the App Store reports, the sandbox testers, each product's state, the catalog's last confirmation; and whether Xcode's purchases are taken. It exits 1 while a check that is not informational fails: run it again until every line is ✓.
--app <app> the iOS app (default: the environment's one linked app)
palbase purchases:paywalls
The environment's paywalls: each one's draft revision, the version published, and the placements that show it
palbase purchases:paywalls
A paywall is a JSON definition: purchases:paywalls:get writes one to a file, purchases:paywalls:put saves the edit as its draft, purchases:paywalls:publish makes the draft what the apps draw. An app sees a paywall once it is published and a placement shows it. How a definition is written — every component and field, and three to start from: palbase purchases:paywalls:guide
palbase purchases:paywalls:get
Write a paywall's draft — or a version published — to a file to edit, with the revision it is at
palbase purchases:paywalls:get <name|id> [--out <palbase/paywalls/name.json>] [--version <n>]
The file is the definition alone, as purchases:paywalls:put takes it, under palbase/paywalls/. Its first line names its JSON Schema, written beside it (palbase/paywalls/paywall.schema.json), so an editor checks the file as it is edited. The revision printed is the one an edit is saved on: purchases:paywalls:put --revision.
--out <file> where under palbase/ (default: palbase/paywalls/<name>.json)
--version <n> a published version instead of the draft
palbase purchases:paywalls:validate
Check a paywall definition as publishing it would, without saving it: every problem, by its path
palbase purchases:paywalls:validate --file <paywall.json>
Every rule of the schema is checked, and every package against the environment's catalog as it is. A warning (what App Review expects of a paywall, say) does not stop a save or a publish. It exits 1 while a problem that is not a warning is left; --json prints { valid, problems } either way.
--file <paywall.json> the definition ('-' or none: stdin)
palbase purchases:paywalls:put
Save a definition as a paywall's draft, on the revision it was edited from — or make the paywall, at revision 0; --publish publishes it too
palbase purchases:paywalls:put <name|id> --file <paywall.json> --revision <n> [--publish]
The module checks it first: a definition that breaks a rule is refused with every problem, and nothing is saved. Of two people editing at once, the second is told — the draft moved past the revision they edited — never overwritten. What the apps draw changes only when the draft is published (--publish, or purchases:paywalls:publish). A new paywall takes the name its definition gives. How a definition is written: palbase purchases:paywalls:guide
--file <paywall.json> the definition, as purchases:paywalls:get wrote it ('-' or none: stdin)
--revision <n> the draft revision it was edited from, as purchases:paywalls:get printed it (0 makes a new paywall)
--publish publish the saved draft as the next version
palbase purchases:paywalls:publish
Publish a paywall's draft as its next version: what the apps asking for a placement that shows it draw from then on
palbase purchases:paywalls:publish <name|id> [--revision <n>]
It is checked as the apps will get it: every package must be sold by the catalog, every asset still what was registered. --revision names the draft revision that was looked at: a draft saved since is not published in its place. Without it, the draft as it is now is published. A draft the same as the newest version publishes nothing new.
--revision <n> the draft revision looked at (default: the draft as it is now)
palbase purchases:paywalls:delete
Delete a paywall and its versions; one a placement or a running experiment shows is not deleted
palbase purchases:paywalls:delete <name|id>
palbase purchases:paywalls:schema
The JSON Schema of a paywall definition: every component, its fields and their bounds, for an editor or an agent to check a file against
palbase purchases:paywalls:schema [--out <palbase/paywalls/paywall.schema.json>]
It is the module's contract, as the CLI was built with it: a file that passes may still be refused for what only the module knows (the catalog's packages, the project's assets) — purchases:paywalls:validate asks it. purchases:paywalls:get writes it too, and names it on the first line of each file ("$schema").
--out <file> where under palbase/ (default: palbase/paywalls/paywall.schema.json); --json prints it instead
palbase purchases:paywalls:guide
How a paywall definition is written by hand — for an agent or a person: the flow, every component and field in the contract's words, three definitions to start from
palbase purchases:paywalls:guide [--out <palbase/paywalls/GUIDE.txt>]
It is made from the definition's JSON Schema the CLI carries (purchases:paywalls:schema), so it says what this version of the CLI checks.
--out <file> write it to a file under palbase/ instead of printing it
palbase purchases:paywalls:assets
The project's paywall assets: the images, videos, fonts and HTML blocks a definition names asset:<id>
palbase purchases:paywalls:assets
palbase purchases:paywalls:assets:upload
Upload a file to the project's storage and register it for paywalls: it answers the asset:<id> a definition names
palbase purchases:paywalls:assets:upload <file> [--kind image|video|font|html]
The file goes to the project's public bucket paywall-assets (made when there is none; a private one is left as it is, and refuses). The module then reads it there, says what it is by its bytes, and keeps their SHA-256: an app draws what it downloads only when it is that. An image is PNG, JPEG or WebP, at most 2 MiB; a video MP4 or MOV, 50 MiB; a font TTF, OTF or WOFF2, 5 MiB; an HTML block a zip with index.html at its root, 5 MiB.
--kind <kind> what the file is meant to be: one whose bytes are another kind is refused
palbase purchases:paywalls:assets:delete
Forget a paywall asset no paywall names, and remove its file from the project's storage
palbase purchases:paywalls:assets:delete <asset:id|id>
palbase purchases:paywalls:preview
A picture of a paywall as an app draws it — the SDK's own renderer, in a headless browser — to look at before it is saved
palbase purchases:paywalls:preview <name|id> | --file <paywall.json> [--platform ios|android|web] [--appearance light|dark] [--locale <tag>] [--text-size default|largest] [--package <key>] [--state tab=<id>,switch=on|off,sheet=<id>] [--full] [--all] [--out <png>] [--html <file>]
It draws the file being edited (--file), or a paywall's draft by its name. The picture is the platform's screen: its status bar and home indicator kept clear, as on a phone. --full draws the whole paywall instead: the part that scrolls in full, the header and the footer once.
Prices are the environment's catalog's, as the platform sells each package; a package it does not sell there is drawn at a sample price, and named. The project's assets are downloaded and drawn when their bytes are the ones registered.
It needs Chrome, Chromium, Edge or Brave (PALBASE_BROWSER names another; PALBASE_BROWSER_ARGS adds to its arguments, as --no-sandbox in a container that has no sandbox for it). Without one the page is written as HTML to open, and it exits 3; when the browser or the renderer cannot draw it, 4.
--all draws every platform in both appearances at both text sizes into one folder (palbase/paywalls/.preview/<name>/). --html writes the page alone, to open in a browser. purchases:paywalls:check measures what is drawn and lists what is wrong.
--file <paywall.json> the definition to draw ('-': stdin)
--locale <tag> the reader's language (default: the definition's default_locale)
--package <product.plan> the package selected
--state <k=v,…> tab=<id> (every tabs component on it), switch=on|off, sheet=<id> (open)
--width <px> the screen's width in CSS pixels (default: the platform's)
--height <px> the screen's height (default: the platform's)
--platform <platform> ios (default), android or web: the screen and the platform's rules
--appearance <appearance> light (default) or dark
--text-size <size> default, or largest: every text at the largest size a reader sets
--full the whole paywall, as tall as it is, not the screen alone
--all every platform × appearance × text size, into one folder
--out <file> the PNG under palbase/ (default: palbase/paywalls/.preview/<name>-<platform>-<appearance>.png); with --all, the folder
--html <file> write the page alone, as HTML under palbase/, to open in a browser
palbase purchases:paywalls:check
Measure a paywall as an app draws it, on every screen, and list what is wrong — code — where — what — exiting 1 while anything is
palbase purchases:paywalls:check <name|id> | --file <paywall.json> [--platform …] [--appearance …] [--text-size …] [--locale <tag>] [--package <key>] [--state …]
It draws the paywall with the SDK's own renderer in a headless browser — every platform, light and dark, at the default and the largest text size, unless a flag names one — and measures what shows: overflow_x: wider than the screen · text_cut: a text cut off · word_broken: a word (a price) broken across lines · purchase_hidden: no purchase button on the screen as it opens · covered_by_footer: an interactive part still under the fixed footer with the paywall scrolled to its end · large_gap: an empty band over 12% of the screen · contrast: text under WCAG's 4.5:1 (3:1 large) · selection_contrast: the selected package or tab under 3:1 from the others · small_target: a target under 44×44 on a phone, 24×24 on the web (a switch, a link inside a sentence and a carousel's dots aside) · image_missing: an image that did not load · repeated_text: the same words twice in one package. Each names the component by its path in the definition (root.children[2]) and the words it shows. A text over a picture or a gradient is said apart, as not measured (unmeasured_contrast): a note, not a problem. --json prints { problems: [{ code, where, message, screens }], notes: [ … ], screens }. It exits 0 when nothing is wrong, 1 when something is (and on a refusal: --json then prints { error } instead), 3 without a browser, 4 when the browser or the renderer could not draw the paywall.
--file <paywall.json> the definition to draw ('-': stdin)
--locale <tag> the reader's language (default: the definition's default_locale)
--package <product.plan> the package selected
--state <k=v,…> tab=<id> (every tabs component on it), switch=on|off, sheet=<id> (open)
--width <px> the screen's width in CSS pixels (default: the platform's)
--height <px> the screen's height (default: the platform's)
--platform <platform> ios, android or web alone (default: all three)
--appearance <appearance> light or dark alone (default: both)
--text-size <size> default or largest alone (default: both)
palbase purchases:paywalls:templates
The module's designed paywalls to start from: each one's key, what it looks like, and the packages it sells
palbase purchases:paywalls:templates
palbase purchases:paywalls:new <name> --template <key> writes one to a file, selling the catalog's own packages.
palbase purchases:paywalls:new
Write a new paywall's definition to a file from one of the module's templates, selling the catalog's own packages, its pictures uploaded to the project
palbase purchases:paywalls:new <name> --template <key> [--package <template package>=<product.plan> …] [--out <palbase/paywalls/name.json>]
A template sells pro.monthly, pro.yearly, family.monthly, family.yearly and lifetime.once: each is renamed to the catalog's package that stands for it — the same one when the catalog sells it, else a plan of the same period. One that nothing stands for is said, and stays: --package pro.yearly=<product>.<plan> names it.
A template's pictures are uploaded to the project's public bucket paywall-assets (made when there is none; a private one is left as it is, and nothing is written) and registered; the definition names the project's assets in their place.
Nothing is saved to the environment but those assets: the file is edited, checked and looked at (purchases:paywalls:validate, :preview, :check), then saved with purchases:paywalls:put <name> --file <file> --revision 0.
--template <key> the template, as purchases:paywalls:templates lists them
--package <from=to> a template's package and the catalog's that stands for it (repeat it, or a comma list)
--out <file> where under palbase/ (default: palbase/paywalls/<name>.json)
palbase purchases:placements
The environment's placements: each one's default paywall, its rules and the experiment running on it
palbase purchases:placements
An app asks for a placement by its key (pb.purchases.paywall("onboarding")): an experiment running for everyone there decides first, then the first of its rules that holds for the viewer, then its default paywall.
palbase purchases:placements:set
Give a placement its default paywall — what it shows whoever none of its rules holds for — making the placement when it is new
palbase purchases:placements:set <placement> --paywall <name|id>
The apps asking for it get the paywall's newest version they can draw; a paywall never published is nothing yet: they draw their own until it is.
--paywall <name|id> the paywall it shows
palbase purchases:placements:delete
Forget a placement and its rules: the apps that ask for it draw their own paywall
palbase purchases:placements:delete <placement>
palbase purchases:placements:rules:get
A placement's rules, in the order they are read — each with its conditions, schedule and target — and their revision
palbase purchases:placements:rules:get <placement> [--out <palbase/….json>]
--out writes them to a file to edit ({ "rules": [ … ] }); rules:put saves the edit on the revision printed.
--out <file> write the rules to this file, under palbase/
palbase purchases:placements:rules:put
Replace a placement's rules — all of them, in the order they are to be read — on the revision they were edited from
palbase purchases:placements:rules:put <placement> --file <rules.json> --revision <n>
The first rule in effect whose conditions all hold for a viewer shows its target: a published paywall, or the variant of the experiment running on the placement for the rules. At most 50 rules of at most 10 conditions. A condition reads country, platform, app, app_version, sdk_version, locale, entitlement, first_seen_days or an attribute the app sends; purchases:placements:preview tries rules against a viewer before they are saved (--rules). Of two people editing at once, the second is told — the rules moved past the revision they edited — never overwritten.
--file <rules.json> the rules, as rules:get --out wrote them ('-' or none: stdin)
--revision <n> the revision they were edited from, as rules:get printed it
palbase purchases:placements:preview
What a placement shows a viewer, and why: the experiment, the rule that decided — every rule, condition by condition — or the default
palbase purchases:placements:preview <placement> [--platform ios|android|web] [--country TR] [--app-version 2.1.0] [--sdk palbase-swift@0.2.0] [--locale tr-TR] [--attr key=value …] [--user <id>] [--rules <file>] [--at <RFC3339>]
The viewer is what its app would send. With --user, the user's entitlements and the days since they were first seen are the project's; the user (else an installation) is who an experiment's variant is drawn for. --rules tries rules not yet saved (a rules:put file) instead of the saved ones; --at, a moment (a scheduled rule's start). Nothing is counted or changed.
--platform <platform> ios, android or web
--country <code> the store's country: ISO 3166-1, two or three letters
--app-version <version> the app's version
--sdk <name@version> the SDK the app draws with (palbase-swift@0.2.0)
--locale <tag> the viewer's language (BCP 47)
--attr <key=value> what the app says of the viewer (repeat it, or a comma list)
--user <id> the signed-in user
--rules <file> rules to try instead of the saved ones
--at <RFC 3339> the moment (default: now)
palbase purchases:placements:results
What each of a placement's rules showed and what its viewers did — exposures, viewers, purchases, conversion, revenue — beside the rules since removed, an experiment's variants and the default
palbase purchases:placements:results <placement> [--days <n>] [--window-days <n>] [--sandbox]
What was shown in the last days (30 unless said, at most 90). The rules come in the order they are read, then the rules the placement no longer has, the variants of an experiment for everyone there, its default. A purchase counts once, for what its buyer saw first at the placement, within the window after seeing it (7 days unless said): the purchases the stores confirmed, never what an app reports. Revenue is what was paid to date, renewals in, refunds out.
--days <n> the days of what was shown the results look back over, 1 to 90 (default 30)
--window-days <n> the days after first seeing what was shown a purchase counts for it, 1 to 90 (default 7)
--sandbox count the stores' test purchases too
palbase purchases:placements:attributes
The attribute keys the environment's apps sent with a placement's paywall, as they said them — suggestions for attribute conditions, the most used first, each with its JSON type and the hours it was seen in
palbase purchases:placements:attributes
An app sends what it says of the viewer as attributes (pb.purchases.paywall(placement, { attributes })); the keys are kept, never the values. They are what apps said, unverified — anyone with the publishable key can send one —: a help to write rules, not a list of what the app defines. A key is seen again at most once an hour, and counted in one more hour as often. At most 200 are kept: past them a new key takes the place of the key seen longest ago once that one was last seen over a day ago, so keys sent once age out.
palbase purchases:experiments
The environment's paywall experiments: the placement each runs on, its variants and their shares, and how it ended
palbase purchases:experiments [--placement <placement>]
--placement <placement> one placement's alone
palbase purchases:experiments:start
Start an experiment on a placement: each viewer is shown one of 2 to 4 published paywalls, by weight, the same one every time
palbase purchases:experiments:start <placement> --variants <paywall>=<weight>,<paywall>=<weight>[,…] [--audience placement|rules] [--name <name>]
audience placement (the default): everyone at the placement, decided before its rules; the placement is made when new and keeps its default for when the experiment ends. audience rules: only the viewers the placement's rules send to it — a rule's target names the experiment (purchases:placements:rules:put). The first variant is what a stop keeps. One experiment runs on a placement at a time; each variant's paywall must be published.
--variants <paywall=weight,…> the paywalls (name or id) and their weights; key:paywall=weight names a key (else a, b, c, d)
--audience <audience> placement (default) or rules
--name <name> the experiment's name (default: the placement and its paywalls)
palbase purchases:experiments:stop
Stop a running experiment, at once: what it showed becomes its first variant's paywall; its results stay
palbase purchases:experiments:stop <id|name>
palbase purchases:experiments:winner
End a running experiment with its winner: the winner's paywall becomes the placement's default — or, with --as-rule, a rule shows it to everyone
palbase purchases:experiments:winner <id|name> --variant <key> [--as-rule]
For an experiment for everyone at the placement, --as-rule rolls the winner out as a live rule at the top of its rules (to narrow later) and leaves the default as it is. For one for the rules, the rules that targeted it show the winner's paywall. purchases:experiments:results says which variant leads and whether calling it is sound yet.
--variant <key> the winning variant's key
--as-rule roll the winner out as a rule for everyone, at the top of the placement's rules
palbase purchases:experiments:results
What each variant's viewers did — exposures, viewers, purchases, conversion, revenue — each one's chance to convert best, and whether calling the leader is sound
palbase purchases:experiments:results <id|name> [--window-days <n>] [--sandbox]
A purchase counts for the first variant its buyer saw, within the window after seeing it (7 days unless said): the purchases the stores confirmed, never what an app reports. Revenue is what was paid to date, renewals in, refunds out. The chance to win is Bayesian (each variant's rate a Beta distribution): how likely each one is best. The leader is called soundly once every variant has been seen enough and its chance is high enough; until then the reasons are said.
--window-days <n> the days after first seeing a variant a purchase counts for it, 1 to 90 (default 7)
--sandbox count the stores' test purchases too
palbase purchases:charts
The purchase charts, each with its definition: what it counts, over which period, and how refunds, product changes, resubscriptions and test purchases count
palbase purchases:charts
A chart is read with purchases:charts:get <chart>; the figures of now with purchases:charts:overview.
palbase purchases:charts:overview
The project's figures at a glance: MRR, active subscriptions and trials now; revenue, new customers and active customers over the last 28 days
palbase purchases:charts:overview [--revenue-type gross|net_of_taxes|proceeds] [--currency <code>] [--sandbox]
--revenue-type <type> gross (default), net_of_taxes or proceeds
--currency <code> the currency to report money in (default: the reporting currency)
--sandbox count the stores' test purchases too
palbase purchases:charts:get
One purchase chart, period by period — a table of its periods and segments, or the module's CSV
palbase purchases:charts:get <chart> [--from <YYYY-MM-DD>] [--to <YYYY-MM-DD>] [--resolution day|week|month] [--segment <dimension>] [--filter <dimension:value>]… [--revenue-type <type>] [--currency <code>] [--window-days <n>] [--cohort new_customers|new_paying_customers] [--sandbox] [--csv [--out <palbase/….csv>]]
Days are UTC; from and to are both included, at most two years apart (by default the last 30 days, 12 weeks or 12 months). A period marked * is not over: its value may still change.
Money is in the reporting currency (purchases:charts:settings) at the European Central Bank's rate of the day it was paid; proceeds take off the store's commission and Stripe's fee, which the ledger does not hold: they are estimated, and so are taxes a store did not state.
--csv prints the module's CSV (a row a segment and period) — on the terminal its control characters escaped —; with --out it is written to that file, under palbase/. A cell that would start a spreadsheet formula (=, +, -, @) begins with a quote.
--from <YYYY-MM-DD> the first day
--to <YYYY-MM-DD> the last day, included (default today)
--resolution <resolution> day (default), week or month
--segment <dimension> split by a dimension (purchases:charts lists them)
--filter <dimension:value> keep only this value, one each --filter: --filter store:app_store --filter country:TR
--revenue-type <type> gross (default), net_of_taxes or proceeds
--currency <code> the currency to report money in (default: the reporting currency)
--window-days <n> the conversion window or customer lifetime, in days from a customer's first day, 0 to 730 (default: to now)
--cohort <cohort> the cohort explorer's cohorts: new_customers (default) or new_paying_customers
--sandbox count the stores' test purchases too
--csv the module's CSV instead of a table
--out <file> with --csv, write it to this file, under palbase/
palbase purchases:charts:settings
The charts' settings — the reporting currency, the stores' commission, Stripe's fee and the App Store's fee on web purchases linked out of the app — read, or changed with the flags given
palbase purchases:charts:settings [--currency <code>] [--commission standard|small_business] [--stripe-percent <n>] [--stripe-fixed <amount>] [--stripe-fixed-currency <code>] [--app-store-link-percent <n>] [--app-store-link-days <n>]
Every amount is converted to the reporting currency at the European Central Bank's rate of the day it was paid; the ledger keeps what was paid as it was paid, so a change redraws every chart from its first day. The commission is what proceeds take off App Store and Google Play payments: standard (30%; 15% of a subscription's renewals after its first paid year in the App Store, and of every Google Play subscription) or small_business (15%). Stripe's fee — its share and fixed part — is taken off web payments; the ledger does not hold it, so proceeds are estimated. The App Store's link fee is what Apple takes of a web purchase bought through a link out of the iOS app in its United States storefront, within the days after the link: nothing today (App Review Guideline 3.1.1(a)), set here once Apple charges one.
--currency <code> the reporting currency, one the European Central Bank published a rate for in the last ten days
--commission <commission> standard (30%) or small_business (15%)
--stripe-percent <n> Stripe's share of a payment, in percent (2.9)
--stripe-fixed <amount> Stripe's fixed fee of a payment (0.30)
--stripe-fixed-currency <code> the currency of Stripe's fixed fee (USD)
--app-store-link-percent <n> the App Store's share of a web purchase linked out of the iOS app, in percent (0)
--app-store-link-days <n> for how many days after the link a purchase owes it (7)
journeys
palbase journeys:get
A journey's definition, as JSON: to edit and push again (a draft), or to push under a new key (a published one)
palbase journeys:get <key>
palbase journeys:validate
Check a journey file without saving it: every problem, at its path, with what to do about it
palbase journeys:validate <file|->
palbase journeys:push
Save a journey file as a draft — new, or the draft's new version — and, with --publish, publish it
palbase journeys:push <file|-> [--key <key>] [--publish]
The key is --key, else the file's name (winback.json is winback). A published journey does not change: push the file under a new key with --publish, then stop the old one (journeys:stop).
--key <key> the journey's key (default: the file's name)
--publish publish it once saved: people enter from then on
palbase journeys:publish
Publish a draft: people enter it from now on, and it no longer changes
palbase journeys:publish <key>
palbase journeys:simulate
Walk a made-up person through a journey — a file, or a key — as it would run: each step, when, what it would do, how it ends; nothing is sent
palbase journeys:simulate <file|key> [--user <id>] [--property name=value]… [--audience <id>]… [--timezone <zone>] [--event name@PT2H]… [--start <time>]
The person enters at --start (now unless it says) and does each --event a while after entering: --event purchases.purchase.completed@P1D. Their --property values are what branches read, --audience the audiences they are in, --timezone where a wait until a time of day is.
--user <id> the person's id (it picks their way at a split)
--property <name=value> a property of theirs
--audience <id> an audience they are in
--timezone <zone> their IANA time zone (Europe/Istanbul)
--event <name@while> an event of theirs, a while after entering (PT2H, P1D)
--start <time> when they enter (RFC 3339); now unless it says
palbase journeys:stats
What came of a journey: who entered, how their runs ended, who is in it now, its goal's conversions, each step's outcomes
palbase journeys:stats <key>
palbase journeys:runs
A journey's people: each run's state, the step it is at, how it ended, and the steps it took
palbase journeys:runs <key> [--user <id>] [--state held|active|waiting|ended] [--limit <n>]
--user <id> one person's runs
--state <state> held, active, waiting or ended
--limit <n> how many, newest first (default 50, at most 200)
palbase journeys:stop
Stop a live journey for good: no one enters it, the runs in it end, its report stays
palbase journeys:stop <key>
palbase journeys:copy
Copy a journey's definition into a new draft: how a published journey is changed
palbase journeys:copy <key> <new-key> [--name <name>]
--name <name> the copy's name (default: the journey's)
palbase journeys:delete
Delete a journey with its runs and its report; the people in a live one leave it at once
palbase journeys:delete <key>
apps
palbase apps:list
The project's apps, and what each is in every environment it is set in
palbase apps:list
palbase apps:create
Register an app of the project (an iOS app names its Apple team); then set it in each environment (apps:env:set)
palbase apps:create ios|android|web <name> [--slug <slug>] [--team-id <id>] [--app-id-prefix <prefix>]
An app is one product on one platform: its widgets, notification service and other extensions are not apps of their own — Apple gives them bundles under the app's, and they share its sign-in.
The slug is the app's file in a checkout: palbase/environments/<env>/<slug>.json.
--slug <slug> its name in a checkout (default: from the name)
--team-id <id> an iOS app's Apple team: 10 characters, Apple Developer › Membership
--app-id-prefix <prefix> an iOS app's App ID prefix when it is not the team's (an app made before 2011, or moved from another team)
palbase apps:get
An app of the project, with what it is and its key in every environment it is set in
palbase apps:get <app>
palbase apps:update
Rename an app, or change an iOS app's Apple team: every environment it is in is told
palbase apps:update <app> [--name <name>] [--team-id <id>] [--app-id-prefix <prefix>|""]
--name <name> what it is called
--team-id <id> an iOS app's Apple team
--app-id-prefix <prefix> its App ID prefix; "" for the team's
palbase apps:delete
Delete an app: suspended in every environment for 30 days — its builds stop reaching the project — then removed for good
palbase apps:delete <app>
palbase apps:restore
Bring a deleted app back as it was, with its keys, in every environment
palbase apps:restore <app>
palbase apps:env:set
What an app is in the environment: its bundle (iOS), package and signing certificates (Android) or origins (web)
palbase apps:env:set <app> [--bundle <id> [--app-clip <id>] [--watch <id>]] [--package <id> --sha256 <label>=<fingerprint>…] [--origin <url>… --redirect <url>…]
iOS: the app's bundle in this environment; its App Clip and watch app are the bundle followed by one more part. Its extensions are not set: they share the app's. Android: its application ID and the SHA-256 of each certificate it is signed with — Play Console › App signing (Google's app signing key, not the upload key), ./gradlew signingReport for a debug key. Label each: debug, play_signing, upload… Web: the origins its pages are served from (https; http only on this computer) and the addresses a sign-in may return to. The platform publishes it to Apple and Google for the environment: a device sees a change in a day or so (Apple caches its file; Android 15 re-checks within a week).
--bundle <id> an iOS app's bundle identifier here
--app-clip <id> its App Clip's bundle (the bundle followed by one more part)
--watch <id> its watch app's bundle (the bundle followed by one more part)
--package <id> an Android app's application ID here
--sha256 <label=fingerprint> a signing certificate's SHA-256, labelled (play_signing=AB:CD:…); every one the app is signed with
--origin <url> a web app's origin
--redirect <url> an address a sign-in may return to
palbase apps:env:unset
Take an app out of the environment: its key there revoked, Apple's and Google's files no longer naming it
palbase apps:env:unset <app>
palbase apps:sync
Write each environment's settings and backend clients (Palbe) of this checkout's apps under palbase/ — nothing else is touched
palbase apps:sync [<app>…] [--check] [--types <env>] [--backend [<machine>=]<dir>] [--as <machine>=<name>]…
The apps named join this checkout's (palbase/project.json); with none named, its apps are synced.
palbase/environments/<env>/: each app's settings (<app>.json), each backend's contract (contracts/<machine>.openapi.json) and Palbe client made from it — TypeScript for a web app, Swift for an iOS app (in palbase/<app>.palbase), Kotlin for an Android app. A web app imports pb from palbase/<app>.ts: one environment's for the editor (--types), the build's mode's for the build (palbase() in vite.config), which type-checks the app against that environment.
--check writes nothing: it exits 1 naming what differs from the project now — for CI.
--backend makes a backend's Palbe from its checkout on this computer (its palbase/backend app writes its contract and exits; nothing is connected to), to try an endpoint before any machine serves it.
A backend is pb.<name> in the apps — pb.shopApi.rooms.create() for the machine shop-api — unless --as names it (--as shop-api=shop): the name is the checkout's, the same in every environment, and none of the client's own (auth, flags, storage, …).
--check write nothing; exit 1 when the checkout is not what the project gives now
--types <env> the environment the editor's types are of (remembered in palbase/project.json)
--backend <[machine=]dir> a backend's checkout here: its Palbe made from the contract it writes, not from its machine's
--as <machine=name> a backend's name in the apps (pb.<name>): its machine's camelCased unless named; the same in every environment (remembered in palbase/project.json)
palbase apps:sdkconfig
An app's settings in the environment, as palbase apps:sync writes them: its address, its publishable key, what its modules tell it
palbase apps:sdkconfig <app> [--format json|env]
The publishable key is public: it ships in the app. A secret key never does.
--format <format> json (default) or env
github
palbase github:connect
Link the environment to GitHub: Palbase Cloud's GitHub app on the account whose repositories machines are built from
palbase github:connect [--device | --github-token-stdin | --from <env> [--repo <owner/name>…]] [--installation <account|id>] [--all-envs]
Opens GitHub; back from it, the page shows the account and you link it by writing the environment's ref.
--from <env>: the installation another environment of the organization linked (its slug in this project, or its ref), linked here too with no GitHub sign-in — a program that opens a project for each product (an admin, or an admin access token). The environment reaches what the GitHub user who linked it there can push to.
--repo <owner/name> (given again for more): with --from, the environment reaches those repositories of the installation alone, never one it reaches later — a product's environment builds nothing of another product's.
--device: no browser here — GitHub's device flow (a code you enter at github.com/login/device on any device); the GitHub token stays in memory.
--all-envs links every environment of the project the same way.
--device GitHub's device flow, for a computer with no browser (a server, an agent)
--github-token-stdin a GitHub user's token from stdin (CI): the installations it reaches, no browser and no code
--installation <account|id> which installation to link, when the GitHub user reaches more than one (--device)
--all-envs link every environment of the project
--from <env> another environment of the organization whose linked installation this one shares (its slug in this project, or its ref): no GitHub sign-in
--repo <owner/name> with --from: the repositories it reaches of the installation, and no other (given again for more)
palbase github:list
The GitHub accounts linked to the environment, and who linked each
palbase github:list
palbase github:repos
The repositories machines of the environment may be built from
palbase github:repos
palbase github:disconnect
Unlink a GitHub account from the environment; its repositories are no longer built
palbase github:disconnect <account|installation-id>
machines
palbase machines:sizes
The machine sizes, and which of them this environment may open now
palbase machines:sizes
palbase machines:list
The environment's machines: size, state, the version serving, address
palbase machines:list
palbase machines:call
Call a route of a machine's backend as its apps do: the fields its contract marks encrypted sealed, its answer's opened
palbase machines:call <machine> <METHOD> <path> [--data <json>] [--token <access token>]
palbase machines:call api POST /patients --data '{"name":"Ada","tc":"10000000146"}' The route is found in the machine's contract; a field it marks encrypted (palbase/backend's encrypted() and encrypt()) goes sealed to the project's key in use and comes back opened, as the app's generated client does. --token is a user's access token, for a route that takes one; without it the call is nobody's.
--data <json> the request's body, as JSON
--token <token> a user's access token: the call is theirs
palbase machines:get
A machine in full: its source, size, environment, database and the build serving
palbase machines:get [name]
palbase machines:create
Make a machine from the code in this directory, built on every push to its branch
palbase machines:create [name] [--size <size>] [--db <database> --migrations] [--repo --branch --path --context] …
Run in a checkout, it reads the repository from origin, the branch from HEAD and the directory from where you are; flags say otherwise. The first build starts at once; --wait follows it until the machine serves, and prints its address. The environment needs its GitHub link first: palbase github:connect.
--repo <owner/name> the GitHub repository (default: this checkout's origin)
--branch <branch> the branch whose pushes are built and rolled out (default: the one checked out)
--path <dir> the directory built, from the repository's root (default: this directory's place in it)
--context <dir> what the build is given above --path (a monorepo: ".")
--size <size> its size (palbase machines:sizes lists them)
--replicas <n> how many copies run, each on a node of its own
--port <port> the port it listens on (it also finds it in PORT)
--health <path> where it answers 2xx while well; a release that does not is not rolled out
--openapi-path <path> where its app serves its OpenAPI contract, when it is not a Palbase backend (apps:sync makes its client from it); "" for a Palbase backend's own
--db <database> a database of the environment it is given (PALBASE_DB_URL); "" for none
--migrations run the database's migrations before each release (a Palbase backend's)
--secrets <production|development> the secrets it is started with, by name; "" for none
--var <K=V> a setting it is started with (repeat it); secrets go in secrets:set
--test-command <command> what each new build runs before it takes traffic, in its image, against the new version itself once its migration has run (bun test, npm test); a failure fails the build and the machine runs what it ran; "" for none
--test-users <names> the test users each test run makes for its tests (api.signInAs), comma-separated; "" for none
--wait follow the first build until the machine serves
palbase machines:update
Change a machine: the change rolls out as a new version beside the one serving
palbase machines:update [name] [--size …] [--replicas …] [--db …] [--repo --branch --path --context] …
--repo <owner/name> the GitHub repository (default: this checkout's origin)
--branch <branch> the branch whose pushes are built and rolled out (default: the one checked out)
--path <dir> the directory built, from the repository's root (default: this directory's place in it)
--context <dir> what the build is given above --path (a monorepo: ".")
--size <size> its size (palbase machines:sizes lists them)
--replicas <n> how many copies run, each on a node of its own
--port <port> the port it listens on (it also finds it in PORT)
--health <path> where it answers 2xx while well; a release that does not is not rolled out
--openapi-path <path> where its app serves its OpenAPI contract, when it is not a Palbase backend (apps:sync makes its client from it); "" for a Palbase backend's own
--db <database> a database of the environment it is given (PALBASE_DB_URL); "" for none
--migrations run the database's migrations before each release (a Palbase backend's)
--secrets <production|development> the secrets it is started with, by name; "" for none
--var <K=V> a setting it is started with (repeat it); secrets go in secrets:set
--test-command <command> what each new build runs before it takes traffic, in its image, against the new version itself once its migration has run (bun test, npm test); a failure fails the build and the machine runs what it ran; "" for none
--test-users <names> the test users each test run makes for its tests (api.signInAs), comma-separated; "" for none
palbase machines:env:list
What a machine is started with, and where each comes from (the platform, the machine, its secrets)
palbase machines:env:list [name]
palbase machines:env:set
Set settings a machine is started with (not secrets: secrets:set keeps those)
palbase machines:env:set K=V… [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:env:unset
Take settings away from what a machine is started with
palbase machines:env:unset K… [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:builds
A machine's builds, newest first
palbase machines:builds [name]
palbase machines:builds:log
A build's log (the newest build's when none is named)
palbase machines:builds:log [build] [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:logs
A machine's log: the last hour, or since a time; -f follows what it writes next
palbase machines:logs [name] [-f] [--since <time>] [--lines <n>] [--search <text>]
-f, --follow keep printing what the machine writes
--since <time> from when: an RFC 3339 time, or 10m, 2h, 1d ago
--lines <n> how many lines at most (default 200)
--search <text> only the lines that hold this text
palbase machines:deploy
Build the head of a machine's branch now and roll it out, as a push does
palbase machines:deploy [name] [--wait]
A push to the branch does this by itself; this is for the one GitHub did not deliver, or a build again. The commit is the branch's head on GitHub: push first.
--wait follow the build until the machine serves it
palbase machines:rollback
Serve an earlier build again (the one before the serving one, or a named one)
palbase machines:rollback [name] [--to <build>]
--to <build> the build to go back to (default: the last one that served before)
palbase machines:crons
The crons a machine runs on a schedule (its app's @Cron classes): when each is next due, on which zone's clocks, and how its last run went
palbase machines:crons [name]
palbase machines:crons:runs
A cron's runs, newest first
palbase machines:crons:runs <cron> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:crons:run
Run a cron now, apart from its schedule — the machine woken for it if asleep
palbase machines:crons:run <cron> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:crons:pause
Pause a cron: it is not run until resumed, whatever version of the machine comes up meanwhile
palbase machines:crons:pause <cron> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:crons:resume
Resume a paused cron: it is due at its next time from now — what it missed is not made up
palbase machines:crons:resume <cron> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:tasks
The tasks on a machine's queue (its app's @Task classes), newest first: of one state, one type, or all
palbase machines:tasks [--machine <name>] [--state waiting|running|succeeded|failed|cancelled] [--task <type>] [--limit <n>]
--machine <name> the machine (default: the only one, or this directory's)
--state <state> only tasks in this state
--task <type> only tasks of this type
--limit <n> the most listed (50; 100 at most)
palbase machines:tasks:types
The task types a machine's running version declares, and what each one's queue holds
palbase machines:tasks:types [name]
palbase machines:tasks:get
One task on a machine's queue, with what it carries
palbase machines:tasks:get <task id> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:tasks:enqueue
Put a task on a machine's queue, as its app's Tasks.enqueue does
palbase machines:tasks:enqueue <type> [--payload '<json>'] [--delay <seconds>] [--key <key>] [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
--payload <json> what it carries: a JSON value (64 KiB at most)
--delay <seconds> due this many seconds from now (30 days at most)
--key <key> put once under this key (held until a day after the task is over)
palbase machines:tasks:retry
Put a task that failed back on the queue, due now, with as many attempts again as its type gives
palbase machines:tasks:retry <task id> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:tasks:cancel
Take a task that is waiting off the queue: it is not run, nor tried again
palbase machines:tasks:cancel <task id> [--machine <name>]
--machine <name> the machine (default: the only one, or this directory's)
palbase machines:contract
The API a Palbase backend serves (its OpenAPI document), as the platform reads it from the machine
palbase machines:contract [name]
palbase machines:delete
Delete a machine: it stops serving, its address answers nothing
palbase machines:delete <name>
databases
palbase databases:sizes
The database sizes, and which of them this environment may open now
palbase databases:sizes
palbase databases:list
The environment's databases: size, state, how full
palbase databases:list
palbase databases:get
A database in full, with how to connect to it
palbase databases:get <name>
palbase databases:create
Open a database; its passwords are kept on this computer (the platform shows them once)
palbase databases:create [name] [--size <size>] [--allow <ip/cidr,…>] [--backup-days <n>] [--show-passwords]
--size <size> its size (palbase databases:sizes lists what is open)
--allow <list> who may connect, as addresses or CIDRs, comma-separated (default: anyone with the password)
--backup-days <n> how many days of backups to keep, 1 to 35
--show-passwords print the passwords too (they are kept on this computer either way)
palbase databases:update
Change a database's size, who may connect to it, or how long its backups are kept
palbase databases:update <name> [--size <size>] [--allow <list>|""] [--backup-days <n>] [--wait]
A new size is taken at once and the database moves to it serving on (palbase databases:sizes lists them): its sessions on the direct port end once, when its primary moves, and new logins wait a few seconds then.
--size <size> the size to move it to
--allow <list> who may connect (comma-separated addresses or CIDRs); "" for anyone with the password
--backup-days <n> how many days of backups to keep, 1 to 35
--wait with --size: follow the move until it serves at the new size
palbase databases:password:reset
Make a database role's password new; it is kept on this computer, the old one stops working
palbase databases:password:reset <name> --role owner|app [--show-password]
The app role's new password reaches the machines given the database as they roll out with it; they keep serving.
--role <role> owner (migrations, the console) or app (what the machines use)
--show-password print it too
palbase databases:restore
Bring a database back to a moment of its backups
palbase databases:restore <name> --to <time>
--to <time> the moment, as an RFC 3339 time (2026-10-08T12:00:00Z)
palbase databases:delete
Delete a database (the platform keeps it 7 days to take back)
palbase databases:delete <name>
palbase databases:undelete
Take back a database deleted in the last 7 days
palbase databases:undelete <name>
palbase databases:purge
Delete a deleted database for good, now
palbase databases:purge <name>
db
palbase db:url
A database's connection address, for any tool (Prisma, Drizzle, pg_dump, a GUI)
palbase db:url [name] [--role owner|app] [--pooled] [--with-password]
Without --with-password the address holds no password: give the tool PGPASSWORD, or pass --with-password and keep the line out of your history. The address asks for a checked certificate (sslmode=verify-full). Node drivers check it against the system's roots by themselves; psql, pg_dump and other libpq tools do only with PGSSLROOTCERT=system (Postgres 16+). The address does not say so itself: a driver that sends what it does not know to the server (postgres.js) would be refused.
--role <role> owner (migrations, dumps) or app (what the machines use); default owner
--pooled the pooled port (PgBouncer, transaction mode): for an app's many short connections
--with-password put the password in the address
palbase db:shell
Psql on a database (it wakes if asleep); -c runs one statement and ends
palbase db:shell [name] [--role owner|app] [--command <sql>]
--role <role> owner (migrations, dumps) or app (what the machines use); default owner
-c, --command <sql> run this and end
palbase db:dump
A copy of a database (pg_dump's custom format), as its owner
palbase db:dump [name] [--file <path>] [--schema-only]
--file <path> where to write it (default <database>-<time>.dump)
--schema-only its structure, not its data
palbase db:restore
Load a dump into a database, as its owner: a custom-format dump (pg_restore) or a .sql file (psql)
palbase db:restore <file> [--db <name>] [--clean]
A database Palbase did not make is taken over by its first release: its tables stay as they are (palbase db:pull writes their schema files).
--db <name> the database (default: the environment's only one)
--clean drop what the dump makes before making it again
palbase db:copy
Copy a database's data from one environment of the project to another
palbase db:copy --from <env> --to <env> [--db <name>] [--to-db <name>]
The target is overwritten with the source: what it held of the same tables goes (pg_restore --clean). An encrypted field (.encrypted(), palbase/server's fields) is copied as •••, sealed with the target's keys: one environment's values never land readable in another.
--from <env> the environment copied from (slug)
--to <env> the environment copied into (slug)
--db <name> the source's database (default: its only one)
--to-db <name> the target's database (default: the same name)
palbase db:plan
What a start of the app's schema would do to a database, and what it would refuse — nothing is changed
palbase db:plan [--url <postgres-url> | --db <name>] [--check]
--check ends with 1 when the schema would be refused: run it in CI before a push to production (-E main).
--url <postgres-url> a database of your own (a local Postgres 18): the schema goes in it at once
--db <name> the environment's database (default: its only one)
--entry <file> the app's entry, what it is started with (default src/main.ts)
--check end with 1 when the plan has refusals
palbase db:status
A database's Palgres ledger: the version serving, and the migration open and who opened it
palbase db:status [--url <postgres-url> | --db <name>]
--url <postgres-url> a database of your own (a local Postgres 18): the schema goes in it at once
--db <name> the environment's database (default: its only one)
--entry <file> the app's entry, what it is started with (default src/main.ts)
palbase db:push
Try the app's schema on a database: your own one at once, the environment's beside the version serving
palbase db:push [--url <postgres-url> | --db <name>] [--reset]
On a database of your own (--url) the migration is started and completed at once; a refused one can --reset (its data goes, the schema is made anew). On the environment's database it is only started: the release serving goes on with its version, your backend with yours; the next release completes it, or takes its place. One migration is open at a time: yours is replaced by your next push; another developer's or a release's is said, and yours waits. Production's schema changes with its releases alone: db:push refuses it (db:plan -E main shows what a release would do).
--url <postgres-url> a database of your own (a local Postgres 18): the schema goes in it at once
--db <name> the environment's database (default: its only one)
--entry <file> the app's entry, what it is started with (default src/main.ts)
--reset with --url: make the schema anew, its data gone
palbase db:migrate
A migration step by hand: start, complete or rollback — for a database whose app runs elsewhere, or to finish one
palbase db:migrate <start|complete|rollback> [--url <postgres-url> | --db <name>]
An app that is not one of the environment's machines: start, release the app, then complete once the old one is gone (pgroll's way).
--url <postgres-url> a database of your own (a local Postgres 18): the schema goes in it at once
--db <name> the environment's database (default: its only one)
--entry <file> the app's entry, what it is started with (default src/main.ts)
palbase db:pull
Write the schema files (db/*.ts) of what a database holds: to take a database over
palbase db:pull [--url <postgres-url> | --db <name>] [--dir <dir>]
Its first release then takes the database over: the tables stay as they are, nothing is copied.
--url <postgres-url> a database of your own (a local Postgres 18): the schema goes in it at once
--db <name> the environment's database (default: its only one)
--entry <file> the app's entry, what it is started with (default src/main.ts)
--dir <dir> where db/ is written (default: here)
palbase db:seed
Run db/seed.ts against a database that is not production
palbase db:seed [--url <postgres-url> | --db <name>] [--file <path>]
--url <postgres-url> a database of your own (a local Postgres 18): the schema goes in it at once
--db <name> the environment's database (default: its only one)
--entry <file> the app's entry, what it is started with (default src/main.ts)
--file <path> the seed script (default db/seed.ts)
palbase db:types
Write the app's tables into palbase/palbase-env.d.ts as types — rows, inserts, unique keys and relations both ways (with, has) — from its schema files
palbase db:types [--entry <file>] [--file <path>]
Database.public.<table> and Database.<schema>.<table> are typed from them with no import and no generic — the relations of a foreign key to another file's table too, which a schema's own type (TablesOf) cannot see: an app that declares Tables with TablesOf leaves that out once it has these. palbase init --db writes the block of its starter schema; run db:types again when a schema changes.
No database is asked: the app is started in its types mode, which reads its schema files and writes nothing else. Only the block between the palbase:env markers is written; run it again when a schema changes.
--entry <file> the app's entry, what it is started with (default src/main.ts)
--file <path> the file (default palbase/palbase-env.d.ts)