Authentication

The BottleCRM API uses JWT access + refresh tokens signed by the backend. Every request to a protected endpoint must carry the access token in the Authorization header.

Obtaining tokens

There are three production paths to a token pair, plus one local-only shortcut.

Google OAuth

The frontend sends the Google ID token it received from the browser:

POST /api/auth/google/
Content-Type: application/json

{ "id_token": "eyJ…" }

Response:

{
  "access": "eyJhbGc…",
  "refresh": "eyJhbGc…",
  "user": { "id": "…", "email": "…" },
  "organizations": [{ "id": "…", "name": "Acme" }]
}

If the user belongs to a single org, the access token already contains the org_id claim. Otherwise the frontend calls POST /api/auth/switch-org/ with the chosen org ID and gets a re-signed pair.

Magic links

POST /api/auth/magic-link/request/
{ "email": "user@example.com" }

The user receives an email with a one-time link. The link's token is exchanged for JWTs:

POST /api/auth/magic-link/verify/
{ "token": "…" }

Dev login (local only)

When DEBUG=True, the management command manage.py devlogin <email> --org <name> prints a ready-to-use token pair. The command refuses to run in production.

Using a token

GET /api/leads/
Authorization: Bearer eyJhbGc…

The token carries the user's profile_id and the active org_id. The backend resolves the profile on every request and sets app.current_org for row-level security.

Refreshing

Access tokens are short-lived (default 15 minutes). Use the refresh token to mint a new access token:

POST /api/auth/refresh/
{ "refresh": "eyJhbGc…" }

Refresh tokens themselves are rotated on use; the response contains a new refresh token that supersedes the old one.

Switching organizations

A user with profiles in multiple orgs can swap tenants without re-authenticating:

POST /api/auth/switch-org/
Authorization: Bearer <current access token>

{ "org_id": "ab12-cd34-…" }

The response is a fresh access + refresh pair with the new org_id baked in. The previous pair becomes invalid for org-scoped queries.

Logging out

The client just discards its tokens. Refresh tokens are server-tracked, so the next refresh attempt with the old token will fail.

To revoke all sessions for a user (e.g. compromised account), rotate JWT_SIGNING_KEY in the backend's environment.