Reference

Authentication

This page is about your app's end users — the people signing into the product you build on Aaly. Connecting an AI coding agent to the platform itself is a separate flow; see Connect an AI agent.

Sign up

POST /<your-slug>/<project>/auth/signup
{ "name": "Ada", "password": "...", "email": "ada@example.com" }

name and password are required; at least one of email, phone, or username identifies the account. Returns 201 with:

{ "jwt": "...", "user": { "id": "USR1", "name": "Ada", "tenantId": "TNT1", "email": "ada@example.com" } }

Tenant behavior on signup depends on the project's publicSignup and multiTenant flags — see Multi-tenancy for the full model. In short: signing up without a tenantId only creates a new tenant at all when the project's publicSignup is true; signing up with an existing tenantId joins that tenant and shares its data with every other user in it. tenantName only names a tenant you're creating — it's ignored when joining an existing one via tenantId.

Sign in

POST /<your-slug>/<project>/auth/signin
{ "identifier": "ada@example.com", "password": "..." }

identifier is an email, phone, or username. Returns the same { jwt, user } shape as signup — unless the identifier belongs to more than one tenant and no tenantId was passed, in which case you get a tenant-selection response instead:

{ "requiresTenantSelection": true, "tenants": [{ "tenantId": "TNT1", "tenantName": "Acme" }] }

Re-send the request with the chosen tenantId to complete sign-in.

Google sign-in

POST /<your-slug>/<project>/auth/google
{ "idToken": "<Google OIDC ID token from your client-side sign-in>" }

Verifies the token against Google, then signs in an existing user matched by email, or registers a new one (auto-creating a tenant). Same tenant-selection response as sign-in if the email matches more than one tenant.

Tokens

  • JWT — returned by signup/signin/Google, HS256-signed, 7-day expiry. Send as Authorization: Bearer <jwt>.
  • API key — aaly_<id>_<secret>, for CI, scripts, and server-to-server calls that shouldn't hold a short-lived user token:
POST /<your-slug>/<project>/auth/keys
Authorization: Bearer <jwt>

Returns the full token once:

{ "id": "KEY1", "token": "aaly_KEY1_xxxxxxxxxxxxxxxx", "createdAt": "2026-08-24T10:00:00.000Z" }

GET /auth/keys lists key metadata (never the secret again); DELETE /auth/keys/{id} revokes one. A JWT and an API key are accepted identically on every endpoint — nothing is JWT-only.

What's not here yet

Password reset ships shortly — see Limits for what's still genuinely missing (refresh tokens, MFA, RBAC).