1. Authentication flow at a glance#
2. Token contract#
| Property | Value |
|---|
| Access token | JWT, typ=developer-portal-access, validity 43200 s (12 h) |
| Refresh token | Opaque string, 30 days, rotated on every /refresh |
| Auth header | Authorization: Bearer <accessToken> |
3. Registration#
Creates the organization (ONBOARDING) + owner + verification (DRAFT)
atomically.{
"companyName": "Acme Payment Solutions",
"legalName": "Acme Payment Solutions LLC",
"email": "dev@company.com",
"phone": "+966501234567",
"fullName": "Sarah Developer",
"password": "SecurePass123"
}
{
"challengeId": "a1b2c3d4e5f60718293a4b5c",
"expiresIn": 600,
"email": "dev@company.com"
}
{ "challengeId": "a1b2c3d4e5f60718293a4b5c", "otp": "123456" }
{ "ok": true, "verified": true }
{ "challengeId": "a1b2c3d4e5f60718293a4b5c" }
Registration does not auto-login. After OTP verification, route your user
through the login flow to obtain tokens.
The registrar (the OWNER) is created ACTIVE immediately — it never passes
through the PENDING state that invited DEVELOPER members do.
4. Login#
{ "email": "dev@company.com", "password": "SecurePass123" }
Response 201 — same challenge shape as registration:{ "challengeId": "a1b2c3d4e5f60718293a4b5c", "expiresIn": 600, "email": "dev@company.com" }
{ "challengeId": "a1b2c3d4e5f60718293a4b5c", "otp": "123456" }
Response 201 — save the tokens:{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"refreshToken": "4f6b2a1c8e9d0f1a2b3c4d5e6f708192",
"tokenType": "Bearer",
"expiresIn": 43200,
"user": { "id": "10", "email": "dev@company.com", "name": "Sarah Developer" },
"organization": {
"id": "42",
"name": "Acme Payment Solutions",
"legalName": "Acme Payment Solutions LLC",
"status": "ONBOARDING"
}
}
Returns profile + organization + application count. Used to confirm a live
session.{
"user": { "id": "10", "email": "dev@company.com", "name": "Sarah Developer", "role": "OWNER" },
"organization": { "id": "42", "name": "Acme Payment Solutions", "status": "APPROVED" },
"applicationCount": 3
}
The role tells you what the account can do: an OWNER is a company admin with
full control; a DEVELOPER can build and maintain the integration but cannot
edit company data, manage the marketplace listing, view refunds, open tickets, or
manage team members.
6. Refresh & logout#
{ "refreshToken": "4f6b2a1c8e9d0f1a2b3c4d5e6f708192" }
Response 200 — pair rotated, previous refresh token revoked:{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"refreshToken": "0a1b2c3d4e5f60718293a4b5c6d7e8f90",
"tokenType": "Bearer",
"expiresIn": 43200
}
{ "refreshToken": "0a1b2c3d4e5f60718293a4b5c6d7e8f90" }
Response 200 — { "ok": true }
7. Password recovery#
8. Authentication error reference#
| Status | Code / scenario | Guidance |
|---|
| 400 | Invalid phone (Saudi +9665…), weak password | Re-validate inputs client-side |
| 400 | Invalid or expired activation link | Ask the owner to resend a fresh link |
| 401 | Wrong password, unknown user → generic login error | Do not reveal which one |
| 401 | Token typ ≠ developer-portal-access | Refresh or re-login on the portal |
| 404 | Order/app not found, or login/direct in production | 404 is the correct behaviour |
| 409 | Account already exists on registration | Route to login instead |
| 409 | Activation: account already activated | Use the forgot-password flow |
| 429 | OTP rate limit exceeded | Wait and retry with backoff |
9. Team management & roles#
Every developer organization starts as a single OWNER account created by
self-registration. The OWNER can invite colleagues as DEVELOPER members who
help with the integration work.Team endpoints require a valid developer-portal Bearer JWT. The activation
endpoint is public — it needs no token.
9.1 Roles#
| Role | Created by | What they can do |
|---|
OWNER | Self-registration | Everything, including team management |
DEVELOPER | Owner invite | Integration work |
Only an OWNER can manage team members; invites always create a DEVELOPER
account — the team API can never create or reassign an OWNER.| Capability | Owner | Developer |
|---|
| Dashboard, company profile, applications, credentials, webhooks, API logs, orders, certification | ✔ | ✔ |
| Edit company profile & verification, upload documents | ✔ | — |
| Marketplace storefront listing (offers, panels, images) | ✔ | — |
| Refunds and support tickets | ✔ | — |
| Manage team members (invite, resend, remove) | ✔ | — |
GET /auth/me returns the member's role, so your app can tailor its UI.A DEVELOPER cannot edit the company profile, manage the marketplace listing,
view refunds, open support tickets, or manage team members.
9.2 Team lifecycle#
| Status | Meaning |
|---|
PENDING | Invited; no password set yet. Cannot log in. |
ACTIVE | Password set and account active. Can log in. |
INACTIVE | Deactivated by the owner. Sessions revoked. |
The OWNER is ACTIVE from self-registration — it never passes through PENDING.9.3 Invite → activate → login#
3.
The invitee signs in through the normal login flow and receives a JWT with
the DEVELOPER role.
9.4 API reference#
Returns every member: the OWNER plus all invited DEVELOPER accounts.Response 200 — TeamMember[][
{
"id": "10",
"email": "owner@acme.com",
"name": "Sarah Developer",
"role": "OWNER",
"status": "ACTIVE",
"invitedByUserId": null,
"inviteExpiresAt": null,
"lastLoginAt": "2026-08-20T09:00:00.000Z",
"createdAt": "2026-07-01T08:00:00.000Z"
},
{
"id": "11",
"email": "alidev@acme.com",
"name": "Ali Mohammed",
"role": "DEVELOPER",
"status": "PENDING",
"invitedByUserId": "10",
"inviteExpiresAt": "2026-08-29T10:00:00.000Z",
"lastLoginAt": null,
"createdAt": "2026-08-26T10:00:00.000Z"
}
]
| Field | Description |
|---|
role | OWNER or DEVELOPER |
status | PENDING | ACTIVE | INACTIVE |
invitedByUserId | Owner who sent the invite (null for Owner) |
inviteExpiresAt | Invitation expiry (null for non-pending) |
Creates a PENDING DEVELOPER member and emails the activation link.| Field | Type | Required | Description |
|---|
email | string | Yes | Invitee email (case-insensitive); must be unused in the portal |
fullName | string | Yes | Invitee display name |
{
"ok": true,
"developerUserId": "11",
"role": "DEVELOPER",
"status": "PENDING",
"inviteExpiresAt": "2026-08-29T10:00:00.000Z",
"emailSent": true
}
Re-inviting an existing PENDING member refreshes the invitation (same
response) instead of creating a duplicate.
If email delivery fails, the created member is rolled back and the request
fails.
Refreshes the invitation expiry and sends a new activation link.{ "ok": true, "inviteExpiresAt": "2026-08-29T11:00:00.000Z", "emailSent": true }
PENDING accounts are deleted outright; ACTIVE/INACTIVE accounts are
deactivated and their sessions revoked. The OWNER can never be removed.Response 200 — { "ok": true }Completes the invite: the invitee clicks the activation link, which opens the
activate page; the page reads the token from the URL and calls this endpoint to
set the password and activate the account.| Field | Type | Description |
|---|
token | string | Activation token from the invite email link (challenge ID) |
newPassword | string | 8–128 chars, at least one letter and one digit (stored with Argon2) |
Response 200 — { "ok": true }The activation link expires after 72 hours; the OWNER must resend after
that.
After activation, future password resets go through forgot-password — the
activation token works only once.
9.5 Team business rules#
| Rule | Behavior |
|---|
| Ownership | Only the OWNER can invite, resend, remove, or list members |
| Role immutability | Invites always create a DEVELOPER; never an OWNER |
| Email uniqueness | An invite email must not already belong to any portal user/org |
| Invite expiry | 72 hours after sending or resending |
| Owner protection | The OWNER account can never be removed |
| Delivery failure | If the invite email cannot be sent, the member is rolled back |
| First login | A member must activate (set password) before they can log in |
9.6 Team errors#
| Status | Code / scenario | Client action |
|---|
| 400 | Not the OWNER | Use the OWNER account |
| 400 | Invalid body, weak password, or mail not configured | Correct the request |
| 400 | Invalid or expired activation link | Ask the owner to resend a fresh link |
| 404 | :userId not found in this organization | Verify the member ID |
| 409 | EMAIL_IN_USE / ALREADY_A_MEMBER on invite | Resolve the conflict |
| 409 | Activate: account already activated | Use the forgot-password flow instead |
9.7 Security considerations#
Removing a member revokes their access immediately.
The activation token is an opaque random string (not a JWT), exposed only
through the invite email link and never returned in an API response.
Team email addresses are company-confidential; treat the GET /team response
accordingly.
10. Summary#
Public endpoints cover register → OTP → login → OTP → tokens, plus
activate for invited DEVELOPER members.
Authenticated calls use Authorization: Bearer with a
developer-portal-access JWT.
Refresh tokens rotate; logout revokes.
Team management and role capabilities live in §9.
You can now obtain and manage a developer session and team. Next — the full
integration flow from onboarding to live.