Authentication
Two credential types, for two audiences:
| Credential | For | Header |
|---|---|---|
| JWT | people (apps with a signed-in user) | Authorization: Bearer <access> |
| API key | systems (your backend, integrations) | Authorization: Api-Key adas_test_… |
People: phone + password
Accounts are phone-first — no email required. SMS one-time codes are
deliberately not the login mechanism (SMS costs money on every African
route; passwords are free forever). OTP exists as a separate one-time
phone-verification step and is currently disabled while pre-launch
(503 OTP_DISABLED).
POST /v1/auth/register {phone, password} → 201 {access, refresh, user}
POST /v1/auth/login {phone, password} → 200 {access, refresh, user}
POST /v1/auth/token/refresh {refresh} → 200 {access}
POST /v1/auth/password/change {current_password, new_password} (JWT)
GET /v1/auth/me (JWT)
Phones must be E.164 (+260971234567); spacing and dashes are tolerated.
Access tokens live 30 minutes; refresh tokens 30 days and rotate on use.
user.phone_verified is false until a verification event (OTP when SMS
launches, or in-person agent attestation). Treat it as a trust signal, not a
gate.
Systems: API keys
Stripe-style: adas_test_… / adas_live_…, shown once at creation, stored
hashed. Manage them with a JWT:
GET /v1/keys/ list your keys (prefixes only)
POST /v1/keys/ {name, mode: "test"|"live"} → includes "key" ONCE
DELETE /v1/keys/{id} revoke
Both modes hit the same data today. Sandbox data isolation for test keys
ships with the billing milestone — build against test keys now and nothing
will change for you.