Auth

Phone-OTP authentication, token lifecycle, and the authenticated user's profile. Paths are relative to the /api/v1 prefix.

See Common errors. The OTP endpoints are rate-limited in production.

Endpoints

Method URI Auth Description
POST /auth/otp/request No Send a one-time login code by SMS
POST /auth/otp/verify No Verify the code; logs in or signs up, returns a token
GET /me Yes Get the authenticated user's profile
PATCH /me Yes Complete/update name & email after onboarding
POST /auth/refresh Yes Rotate the current token
POST /auth/logout Yes Revoke the current token
GET /auth/dev-lookup No · dev-only Look up a user id by phone
POST /auth/dev-token No · dev-only Mint a token for an existing user

dev-lookup and dev-token return 404 in production.


POST /auth/otp/request

Sends a numeric code to the phone. Outside production the response includes the generated code to make testing easy.

Rate limit (production): 3 requests / 10 minutes.

Request body

Field Type Required Rules
phone string yes E.164-ish, ^\+\d{10,15}$ (e.g. +584241000001)

Response 200

{ "sent": true, "mocked": true, "code": "123456" }

code is present only outside production. In production the field is omitted.

Errors

Status Body When
429 { "error": "SmsSendError", "message": "…" } The SMS service failed or was throttled

POST /auth/otp/verify

Verifies the code and authenticates. Creates the account if none exists for the phone.

Rate limit (production): 5 requests / 10 minutes.

Request body

Field Type Required Rules
phone string yes ^\+\d{10,15}$
code string yes exactly 6 digits
app string no client (default) or driver

Response 200

{
  "token": "12|abc...",
  "isNew": false,
  "needsUserData": false,
  "user": {
    "id": 1,
    "name": "Ana MartĂ­nez",
    "phone": "+584241000001",
    "hasClientProfile": true,
    "hasDriverProfile": false,
    "isDriverPending": false
  }
}

Field notes:

Errors

Status Body When
401 { "error": "InvalidOtp", "message": "CĂłdigo incorrecto o vencido." } Wrong or expired code

GET /me

Returns the authenticated user with their profile aggregates.

Response 200

{
  "user": {
    "id": 1,
    "name": "Ana MartĂ­nez",
    "phone": "+584241000001",
    "isActive": true,
    "clientProfile": {
      "totalOrders": 12,
      "totalLitersOrdered": 36000,
      "totalSolidaryUSD": 4.2
    },
    "driver": null
  }
}

For a driver the driver block is populated and clientProfile is null:

{
  "user": {
    "id": 3,
    "name": "Carlos RodrĂ­guez",
    "phone": "+584241001001",
    "isActive": true,
    "clientProfile": null,
    "driver": {
      "id": 3,
      "status": "ACTIVE",
      "isOnline": true,
      "availableLiters": 8000,
      "remainingLiters": 6000,
      "avgRating": 4.8,
      "completedOrders": 27,
      "isCertified": true
    }
  }
}

clientProfile and driver are null when the user lacks that profile. When present, the driver block carries:

Field Type Description
id integer Driver profile id
status string ACTIVE, PENDING_VERIFICATION, SUSPENDED, …
isOnline boolean Whether the driver is currently online
availableLiters integer Water physically in the tank right now
remainingLiters integer Liters still free to commit to new orders
avgRating number Rolling average rating (0–5)
completedOrders integer Lifetime delivered orders
isCertified boolean Whether the driver is certified

availableLiters vs remainingLiters:


PATCH /me

Completes or updates the user's name and email (typically right after OTP sign-up).

Request body

Field Type Required Rules
name string yes 2–80 chars
email string no valid email, ≤120 chars, unique among users

Response 200 — the updated user (same shape as user in otp/verify).


POST /auth/refresh

Revokes the current token and issues a new one.

Response 200

{ "token": "13|def..." }

POST /auth/logout

Revokes the current token.

Response 200

{ "ok": true }

Dev-only endpoints

Available outside production only (otherwise 404).

GET /auth/dev-lookup

Query: phone (string, required).

Response 200

{ "userId": 7, "name": "Carlos RodrĂ­guez", "hasClientProfile": false, "hasDriverProfile": true }
Status Body When
400 { "error": "MissingPhone" } phone not provided
404 { "error": "UserNotFound" } No user for that phone

POST /auth/dev-token

Request body

Field Type Required Rules
userId integer yes existing user id
app string yes client or driver

Response 200

{ "token": "14|ghi...", "user": { "id": 7, "name": "Carlos RodrĂ­guez", "hasClientProfile": false, "hasDriverProfile": true, "isDriverPending": false } }
Status Body When
404 { "error": "UserNotFound" } No user for userId
400 { "error": "NoClientProfile" } app=client but the user has no client profile
400 { "error": "NoDriverProfile" } app=driver but the user has no driver profile

Examples

# 1) Request a code (dev returns it inline)
curl -X POST /api/v1/auth/otp/request \
  -H "Content-Type: application/json" \
  -d '{"phone": "+584241000001"}'

# 2) Verify + log in
curl -X POST /api/v1/auth/otp/verify \
  -H "Content-Type: application/json" \
  -d '{"phone": "+584241000001", "code": "123456", "app": "client"}'

# 3) Use the token
curl /api/v1/me -H "Authorization: Bearer 12|abc..."

# 4) Log out
curl -X POST /api/v1/auth/logout -H "Authorization: Bearer 12|abc..."