Documentation

Actors and authentication

Builder, end user, staff — three distinct identities and which auth endpoint each uses.

Vibe Studio juggles three different humans (and a couple of machine identities). Confusing them is the most common AI integration mistake — the agent calling the wrong signin endpoint, or treating a builder's session as if it belonged to the deployed app's customer.

The three actors

RoleWhoWhere they appearAuth endpoint
BuilderThe Vibe Studio user who owns the workspace. Signed in to studio.appmint.io..vibe/project.jsonowner.emailAlready handled by Vibe Studio. The agent never re-authenticates them.
End user (customer)The consumer of the app being built. Signs up on the deployed app's storefront / login page.Whatever UI the app exposesPOST /profile/customer/signin
Staff (user)An internal AppMint admin operator. Manages tenants, content, and config from the AppMint admin console.Only relevant when appAudience === "staff"POST /profile/user/signin

The default assumption is that every app built in Vibe Studio is customer-facing. Staff tools are rare and almost never built in Vibe Studio. Unless the builder explicitly says "this is an internal staff tool", the audience is customer.

What appAudience controls

The appAudience field in .vibe/project.json decides which auth endpoints the agent generates code for. When the builder asks the agent to "add a sign-in page", the agent reads the audience and writes the right call:

// appAudience: "customer"
const res = await fetch(`${APPMINT_API_URL}/profile/customer/signin`, {
  method: 'POST',
  headers: { orgid: APPMINT_ORG_ID, 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password }),
});

// appAudience: "staff"
const res = await fetch(`${APPMINT_API_URL}/profile/user/signin`, { ... });

Mixing them silently corrupts the auth flow — /profile/user/signin rejects customers, and /profile/customer/signin rejects staff. The widget shipping with a customer audience that calls /profile/user/signin will look like it works in dev and fail the moment a real customer tries to sign in.

Two axes of authentication

Auth has two dimensions, and they're independent:

  • Whocustomer (app end-user) or user (staff). Decided by appAudience.
  • HowBearer token (after signin) or x-api-key header (service-to-service).

Bearer (interactive sessions)

For human users in the deployed app:

POST /profile/customer/signin
orgid: <orgId>
Content-Type: application/json

{ "email": "[email protected]", "password": "..." }

Response:

{ "token": "eyJ...", "customer": { "_id": "...", "email": "..." } }

Subsequent requests:

orgid: <orgId>
Authorization: Bearer <token>
Content-Type: application/json

API key (service-to-service)

When a backend script or cron job needs access:

orgid: <orgId>
x-api-key: <api_key>
Content-Type: application/json
Never bake an API key into client-side code

API keys grant elevated access. They belong on servers. The agent's instructions explicitly forbid putting an x-api-key into a frontend bundle, an inline script, or any code that ships to the browser.

Don't mix them

Authorization: Bearer and x-api-key on the same request is undefined behaviour. AppEngine picks one — usually the API key — but rejecting an "improperly authenticated" request is also valid. Pick one mode and stick with it.

The builder's identity

The builder signs into studio.appmint.io with their AppMint user account. Their JWT is injected into every agent subprocess as APPMINT_TOKEN so the agent can call AppEngine on their behalf:

curl -H "Authorization: Bearer $APPMINT_TOKEN" \
     -H "orgid: $APPMINT_ORG_ID" \
     "$APPMINT_API_URL/dev-env/$APPMINT_PROJECT_NAME"

The agent uses this token for:

  • Reading the dev env record (custom domains, plan limits).
  • Writing planner cards.
  • Discovery API calls.
  • Any AppMint integration the builder is requesting.

It's not the same as the end user's token. End users of the deployed app get their own tokens via /profile/customer/signin once the app is live.

What's NOT in the agent's hands

The agent's instructions are explicit about what it doesn't have:

  • The sfpk_… SpinForge partner key — server-only, owned by session-manager.
  • Any other user's token — scoped per session.
  • Any user's password — never available, never asked for.

If the agent needs to do something that requires one of these, it asks the builder rather than fabricating credentials.

Verifying before writing

The agent's first move on any auth-related task is to hit discovery:

curl "$APPMINT_API_URL/discover/01-auth"

Discovery is always fresh; the agent's static instructions can lag. If you find the agent generating a stale endpoint name, it's because discovery returned something different — trust discovery.

See Discovery API usage for the full pattern.

Common mistakes

  • Using /profile/user/signin in a customer-facing app. The endpoint exists but rejects customers. The agent's instructions explicitly flag this; the audience field is what prevents it.
  • Treating the builder's token as the customer's token. The builder's JWT works for AppMint admin operations from the agent. It's not the right token for the deployed app's end users.
  • Hardcoding an API key into a frontend. Don't. Use signin.
  • Sending both Authorization: Bearer and x-api-key. Pick one.
  • Skipping orgid. Every AppMint call requires it. Missing → 400.

Reference

The canonical text lives in agent-zero/AI-INSTRUCTIONS.md § 2 and § 3. When the docs and the instructions disagree, the instructions win — they're shipped to the agent and govern its behaviour.