01 - Authentication & Team
1. Authentication flow at a glance
sequenceDiagram
autonumber
participant Dev as Developer App
participant API as DigetPay API v1
participant Notify as SMS / Email OTP
Dev->>API: POST /developer-portal/register
API-->>Notify: Send OTP
API-->>Dev: 201 - {challengeId, expiresIn, email}
Dev->>API: POST /developer-portal/register/verify-otp
API-->>Dev: 200 - {ok: true, verified: true}
Dev->>API: POST /developer-portal/auth/login
API-->>Notify: Send OTP
API-->>Dev: 200 - {challengeId, expiresIn, email}
Dev->>API: POST /developer-portal/auth/login/verify-otp
API-->>Dev: 201 - {accessToken, refreshToken, tokenType, expiresIn, user, organization}
Note over Dev,API: Access token valid for 12 hours
Dev->>API: GET /developer-portal/auth/me - Bearer token
API-->>Dev: 200 - {user, organization, applicationCount}
Dev->>API: POST /developer-portal/auth/refresh
API-->>Dev: 200 - pair rotated
Dev->>API: POST /developer-portal/auth/logout
API-->>Dev: 200 - {ok: true}
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
POST /developer-portal/register — public
POST /developer-portal/register — publicCreates the organization (ONBOARDING) + owner + verification (DRAFT)
atomically.
{
"companyName": "Acme Payment Solutions",
"legalName": "Acme Payment Solutions LLC",
"email": "[email protected]",
"phone": "+966501234567",
"fullName": "Sarah Developer",
"password": "SecurePass123"
}Response 201
{
"challengeId": "a1b2c3d4e5f60718293a4b5c",
"expiresIn": 600,
"email": "[email protected]"
}{ "challengeId": "a1b2c3d4e5f60718293a4b5c", "otp": "123456" }Response 200
{ "ok": true, "verified": true }{ "challengeId": "a1b2c3d4e5f60718293a4b5c" }Response 200
{ "expiresIn": 600 }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 thePENDINGstate that invitedDEVELOPERmembers do.
4. Login
POST /developer-portal/auth/login — public
POST /developer-portal/auth/login — public{ "email": "[email protected]", "password": "SecurePass123" }Response 201 — same challenge shape as registration:
{ "challengeId": "a1b2c3d4e5f60718293a4b5c", "expiresIn": 600, "email": "[email protected]" }{ "challengeId": "a1b2c3d4e5f60718293a4b5c", "otp": "123456" }Response 201 — save the tokens:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"refreshToken": "4f6b2a1c8e9d0f1a2b3c4d5e6f708192",
"tokenType": "Bearer",
"expiresIn": 43200,
"user": { "id": "10", "email": "[email protected]", "name": "Sarah Developer" },
"organization": {
"id": "42",
"name": "Acme Payment Solutions",
"legalName": "Acme Payment Solutions LLC",
"status": "ONBOARDING"
}
}5. Session endpoint — GET /developer-portal/auth/me
GET /developer-portal/auth/meReturns profile + organization + application count. Used to confirm a live
session.
{
"user": { "id": "10", "email": "[email protected]", "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
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /developer-portal/auth/forgot-password | Start reset (OTP challenge) |
| POST | /developer-portal/auth/forgot-password/verify | Verify OTP → resetToken |
| POST | /developer-portal/auth/forgot-password/reset | Set the new password |
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.
| Property | Value |
|---|---|
| Roles | OWNER (full control), DEVELOPER (integration work) |
| Team operations | Owner-only |
| Invite expiry | 72 hours |
| Main endpoints | GET /team, POST /team/invite, POST /team/:userId/resend-invite, DELETE /team/:userId, POST /auth/activate |
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
DEVELOPERcannot edit the company profile, manage the marketplace listing,
view refunds, open support tickets, or manage team members.
9.2 Team lifecycle
stateDiagram-v2
[*] --> PENDING : owner invites
PENDING --> ACTIVE : activation link + password
PENDING --> [*] : invite expires / removed
ACTIVE --> INACTIVE : owner removes
| 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
- Owner sends
POST /developer-portal/team/invitewithemailandfullName;
the server creates aPENDINGmember and emails an activation link. - The invitee clicks the link and sets their password via
POST /developer-portal/auth/activatewith thetokenandnewPassword;
the account becomesACTIVE. - The invitee signs in through the normal login flow and receives a JWT with
theDEVELOPERrole.
9.4 API reference
List members — GET /developer-portal/team — Bearer, owner-only
GET /developer-portal/team — Bearer, owner-onlyReturns every member: the OWNER plus all invited DEVELOPER accounts.
curl -X GET \
"https://fin-api.digetpay.com/v1/developer-portal/team" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response 200 — TeamMember[]
[
{
"id": "10",
"email": "[email protected]",
"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": "[email protected]",
"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) |
Invite a DEVELOPER — POST /developer-portal/team/invite — Bearer, owner-only
POST /developer-portal/team/invite — Bearer, owner-onlyCreates a PENDING DEVELOPER member and emails the activation link.
curl -X POST \
"https://fin-api.digetpay.com/v1/developer-portal/team/invite" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"email": "[email protected]",
"fullName": "Ali Mohammed"
}'| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Invitee email (case-insensitive); must be unused in the portal |
fullName | string | Yes | Invitee display name |
Response 200
{
"ok": true,
"developerUserId": "11",
"role": "DEVELOPER",
"status": "PENDING",
"inviteExpiresAt": "2026-08-29T10:00:00.000Z",
"emailSent": true
}- Re-inviting an existing
PENDINGmember refreshes the invitation (same
response) instead of creating a duplicate. - If email delivery fails, the created member is rolled back and the request
fails.
Resend an invitation — POST /developer-portal/team/:userId/resend-invite — Bearer, owner-only
POST /developer-portal/team/:userId/resend-invite — Bearer, owner-onlyRefreshes the invitation expiry and sends a new activation link.
curl -X POST \
"https://fin-api.digetpay.com/v1/developer-portal/team/11/resend-invite" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response 200
{ "ok": true, "inviteExpiresAt": "2026-08-29T11:00:00.000Z", "emailSent": true }Remove a member — DELETE /developer-portal/team/:userId — Bearer, owner-only
DELETE /developer-portal/team/:userId — Bearer, owner-onlyPENDING accounts are deleted outright; ACTIVE/INACTIVE accounts are
deactivated and their sessions revoked. The OWNER can never be removed.
curl -X DELETE \
"https://fin-api.digetpay.com/v1/developer-portal/team/11" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response 200 — { "ok": true }
Activate an invited account — POST /developer-portal/auth/activate — public
POST /developer-portal/auth/activate — publicCompletes 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.
curl -X POST \
"https://fin-api.digetpay.com/v1/developer-portal/auth/activate" \
-H "Content-Type: application/json" \
--data '{
"token": "a1b2c3d4e5f6a7b8",
"newPassword": "secure-pass-2026"
}'| 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 /teamresponse
accordingly.
10. Summary
-
Public endpoints cover register → OTP → login → OTP → tokens, plus
activate for invitedDEVELOPERmembers.Authenticated calls use
Authorization: Bearerwith a
developer-portal-accessJWT. -
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.
Updated about 1 month ago

