Authentication
DEXPIRY uses JWT bearer tokens. There is no API-key header today. Choose the flow that matches the actor.
| Flow | Actor | Endpoint |
|---|---|---|
| Email + password | Merchant, admin | POST /v1/auth/login |
| Claim link | Customer | POST /v1/customer/auth/claim |
| Magic link | Customer | POST /v1/customer/auth/magic-link |
| Request access | Customer (email a new link) | POST /v1/customer/auth/request-access |
Every protected route:
Authorization: Bearer <jwt>
Missing or invalid tokens return 401 with error: "unauthorized". Wrong role (for example a customer token on a merchant route) returns 403 with error: "forbidden".
Merchant and admin login
curl -X POST https://api.dexpiry.com/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "yourPassword"}'
200 response (shape):
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "...",
"name": "...",
"email": "user@example.com",
"role": "MERCHANT_USER",
"merchantId": "...",
"merchantName": "...",
"merchantRole": "OWNER"
}
}
role is one of ADMIN, MERCHANT_USER, CUSTOMER, SERVICE_PROVIDER. Merchant payloads include merchantId and merchantRole (OWNER | ADMIN | USER). Admin payloads include adminRole.
Errors:
| HTTP | error |
When |
|---|---|---|
| 400 | validation_error (or Zod details) |
Invalid email/password body |
| 401 | invalid_credentials |
Unknown email or wrong password |
| 403 | account_suspended |
User status is SUSPENDED |
| 403 | merchant_suspended |
Merchant organisation is not ACTIVE |
Change password (authenticated):
curl -X PUT https://api.dexpiry.com/v1/auth/change-password \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"currentPassword": "old", "newPassword": "newPass1"}'
Customer claim link
Used by the landing page /u/[claimId] after a merchant issues a warranty and the customer opens the e-mail.
- Validate (read-only) —
GET /v1/customer/auth/validate-claim?claimId=<id> - Consume and get JWT —
POST /v1/customer/auth/claim
curl -X POST https://api.dexpiry.com/v1/customer/auth/claim \
-H "Content-Type: application/json" \
-d '{"claimId": "<claim-id>"}'
Optional focusWarrantyId (UUID) highlights a specific warranty in the returned wallet.
Expired links return 401 expired. Invalid links return 401 invalid_link. Resend:
curl -X POST https://api.dexpiry.com/v1/customer/auth/resend-link \
-H "Content-Type: application/json" \
-d '{"claimId": "<claim-id>"}'
Resend is rate-limited (429 too_many_requests).
Customer magic link
Used when the customer requests wallet access from the app (POST /v1/customer/auth/request-access with { "email": "..." }). The e-mail contains a one-time token.
Validate without consuming:
curl "https://api.dexpiry.com/v1/customer/auth/validate?token=<token>"
Consume and receive a JWT:
curl -X POST https://api.dexpiry.com/v1/customer/auth/magic-link \
-H "Content-Type: application/json" \
-d '{"token": "<token>"}'
| HTTP | error |
When |
|---|---|---|
| 401 | invalid_token |
Unknown or malformed |
| 401 | expired_token |
Past expiry |
| 401 | token_used |
Already consumed |
Using the token
curl https://api.dexpiry.com/v1/merchant/stats \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
curl https://api.dexpiry.com/v1/customer/warranties \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Public routes (no JWT)
GET /health,GET /ready,GET /v1/versionGET /openapi.json,GET /docsGET /v1/global/products- Customer auth validate/request/claim/magic-link/resend (the endpoints themselves)
POST /v1/customer/receipts/ocr(OCR helper; saving a receipt to an account still requires a customer JWT)