Skip to content

Console API for agents and CI

Most of what the console does to set a project up is an HTTP call, so a script, a CI job or an AI agent can create an organization, a project, its row rules and its auth settings without a browser. Every route on this page except sign-in takes a console access token in Authorization: Bearer …, not a project API key; see Access tokens. Request bodies are JSON (Content-Type: application/json).

VAIF Studio accounts are created only in the console, where sign-up runs a bot check that a script cannot pass. Create the account there once, confirm its email address, and use it from then on. See Sign up.

POST /auth/cli/login with { "email", "password" } answers { "ok": true, "accessToken", "refreshToken", "expiresIn", "user" }, where accessToken is a CLI token: valid for 30 days, with the cli scope. Every route on this page accepts it; billing and admin routes refuse it.

Terminal window
VAIF_AUTH_TOKEN=$(curl -s -X POST https://api.vaif.studio/auth/cli/login \
-H "Content-Type: application/json" \
-d '{"email":"ci@example.com","password":"a-long-unique-passphrase"}' | jq -er .accessToken)
export VAIF_AUTH_TOKEN

jq -e makes the step fail when the answer has no accessToken, for example after a wrong password or for an account with two-factor sign-in on. Plain jq -r would set the variable to the text null and let the job carry on. The examples below use -e for the same reason.

When the account has two-factor sign-in on, the answer is { "ok": true, "mfaRequired": true, "challengeToken" } instead. Finish with POST /auth/mfa/verify and { "challengeToken", "code" }, using a 6-digit code or a backup code; it answers with the CLI token.

The same token works for the CLI: put it in VAIF_AUTH_TOKEN and every vaif command uses it (see the CLI). Keep it in your CI’s secret store, never in a repository.

POST /orgs with { "name" } (2 to 50 characters; companyName is optional). The caller becomes its owner, on the Free plan. Names do not have to be unique; each organization gets a unique slug.

Terminal window
ORG_ID=$(curl -s -X POST https://api.vaif.studio/orgs \
-H "Authorization: Bearer $VAIF_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Acme CI"}' | jq -er .id)

The answer is the organization (id, name, slug and the rest). Creating one needs an account whose email address is confirmed, and one user can create 5 organizations an hour.

POST /projects with { "name", "orgId" } (name up to 50 characters; slug, description and regionKey are optional). The caller must be an admin or owner of the organization, and the plan must have room for another project; otherwise the answer is 403 and names the limit.

Terminal window
curl -s -X POST https://api.vaif.studio/projects \
-H "Authorization: Bearer $VAIF_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"my-app\",\"orgId\":\"$ORG_ID\"}" > project.json
PROJECT_ID=$(jq -er .project.id project.json)
PUBLISHABLE_KEY=$(jq -er .keys.anon project.json)

The answer carries the project’s keys:

Field What it is
project.id The project ID
keys.anon The publishable key (vk_pub_…), for browser and app code. Row rules apply
keys.service_role The secret key (vk_srv_…). It bypasses row rules, belongs on servers only, and the API refuses it from browser origins
apiKey The publishable key again (an older field)

Store the secret key in your secret store as soon as you have it, and delete project.json.

Send SQL migration files through the schema engine with the CLI: run vaif db push --project-id $PROJECT_ID with VAIF_AUTH_TOKEN set. See Migrations. Or send a schema definition to POST /schema-engine/apply.

POST /rls/policies/{projectId} (project developer or higher) with a claim rule, { "tableName", "name", "operation", "field", "matchClaim" }, or a public rule, { "tableName", "name", "operation", "access": "public" }. operation is SELECT, INSERT, UPDATE, DELETE or ALL, and enabled is optional (on by default). It answers 201 with { "policy" }.

Terminal window
curl -X POST https://api.vaif.studio/rls/policies/$PROJECT_ID \
-H "Authorization: Bearer $VAIF_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tableName":"posts","name":"Authors own their posts","operation":"ALL","field":"author_id","matchClaim":"sub"}'

Give every table that holds user data a claim rule: a table with no rules at all is open to every signed-in user, for reading and writing. With a claim rule that covers inserts, the server fills the owner column (author_id here) from the signed-in user. See Row rules.

PATCH /projects/{projectId}/auth/settings (project admin or higher) with the fields to change. It answers { "settings" }. The two an app usually needs:

Field Value
allowedRedirectUrls The URLs users may be sent back to after social sign-in or a magic link. Matched by exact origin, so list each preview deploy too. The list you send replaces the old one
userTableName The table that signed-up users are copied into (users by default), or null to turn that off
Terminal window
curl -X PATCH https://api.vaif.studio/projects/$PROJECT_ID/auth/settings \
-H "Authorization: Bearer $VAIF_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allowedRedirectUrls":["https://app.example.com","http://localhost:5173"],"userTableName":"users"}'

POST /realtime/install (project developer or higher) with { "projectId", "tables" }, and envId for an environment other than the default. Every table must already exist.

Terminal window
curl -X POST https://api.vaif.studio/realtime/install \
-H "Authorization: Bearer $VAIF_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"$PROJECT_ID\",\"tables\":[\"posts\"]}"

The app needs only the project ID and the publishable key, for example as VITE_VAIF_PROJECT_ID and VITE_VAIF_PUBLISHABLE_KEY, or NEXT_PUBLIC_VAIF_PROJECT_ID and NEXT_PUBLIC_VAIF_PUBLISHABLE_KEY. Never give it the secret key. See AI app builders.