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
| Role | Who | Where they appear | Auth endpoint |
|---|---|---|---|
| Builder | The Vibe Studio user who owns the workspace. Signed in to studio.appmint.io. | .vibe/project.json → owner.email | Already 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 exposes | POST /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:
- Who —
customer(app end-user) oruser(staff). Decided byappAudience. - How —
Bearertoken (after signin) orx-api-keyheader (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
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/signinin 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: Bearerandx-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.