Documentation

UnityAuth API

UnityAuth is the identity provider for every UNX app. It handles accounts, email verification, sign-in, password reset, sessions, roles, tenants and the audit log. This page documents everything live on https://auth.unx.ng today.

Base URL https://auth.unx.ng. Authentication routes live under /v1/auth; UNX platform routes under /v1. All bodies are JSON.

Core concepts

Sessions are cookies
A successful sign-in sets an HttpOnly, Secure session cookie. Browsers send it automatically with credentials: "include". Never copy session tokens into localStorage.
Trusted origins
Requests that change state must carry an Origin header from an allowed site (UNX apps on *.unx.ng). Ask the UNX team to add your app's origin.
Verified email
Email and password accounts must verify their email with a 6-digit code before the password works. Passwordless and social sign-ins arrive verified.
Stable ids
Ids are prefixed and time-sortable: usr_ users, ses_ sessions, org_ tenants, aud_ audit events. Link your data to them.

API reference

Badges show what each call needs: Public no session, Signed in a session cookie, permission a role that grants that permission.

Sign up and email verification

Email and password sign-up creates the account and emails a 6-digit code. The account cannot sign in with its password until the email is verified; verifying signs the user in.

POST/v1/auth/sign-up/emailPublic

Create an account and email a verification code.

Request body
{
  "name": "Ada Obi",
  "email": "ada@example.com",
  "password": "at-least-8-chars",
  "username": "adaobi"          // optional; generated when omitted
}
Response 200
{ "token": null, "user": { "id": "usr_…", "email": "ada@example.com", "emailVerified": false, "username": "adaobi" } }
POST/v1/auth/email-otp/verify-emailPublic

Verify the email with the 6-digit code. Sets the session cookie.

  • Codes expire after 5 minutes and allow 3 attempts.
Request body
{ "email": "ada@example.com", "otp": "123456" }
Response 200
{ "status": true, "token": "…", "user": { "emailVerified": true, … } }
POST/v1/auth/email-otp/send-verification-otpPublic

Send a fresh code (email-verification or sign-in).

Request body
{ "email": "ada@example.com", "type": "email-verification" }   // or "sign-in"
Response 200
{ "success": true }

Sign in

Every method resolves to the same UNX user. A successful sign-in sets an HttpOnly session cookie; there is no token for the browser to store.

POST/v1/auth/sign-in/emailPublic

Email and password.

  • 401 INVALID_EMAIL_OR_PASSWORD for wrong credentials.
  • 403 EMAIL_NOT_VERIFIED when the email is unverified; a fresh verification code is emailed automatically.
Request body
{ "email": "ada@example.com", "password": "…", "rememberMe": true }
Response 200
{ "redirect": false, "token": "…", "user": { … } }
POST/v1/auth/sign-in/usernamePublic

Username and password.

Request body
{ "username": "adaobi", "password": "…", "rememberMe": true }
POST/v1/auth/sign-in/email-otpPublic

Passwordless: sign in with a code sent by send-verification-otp (type sign-in).

  • Creates the account on first use, already verified.
Request body
{ "email": "ada@example.com", "otp": "123456" }
POST/v1/auth/sign-in/socialPublic

Start Google, Microsoft, Apple, GitHub, Facebook, LinkedIn or X sign-in.

  • Only providers listed by GET /v1/providers are enabled.
Request body
{ "provider": "google", "callbackURL": "https://your.app/after-login", "errorCallbackURL": "https://your.app/login" }
Response 200
{ "url": "https://accounts.google.com/…", "redirect": true }
GET/v1/providersPublic

Social providers that are switched on.

Response 200
{ "providers": ["google"] }
GET/v1/auth/get-sessionPublic

The current user and session, or null.

Response 200
{ "user": { … }, "session": { "id": "ses_…", "expiresAt": "…", "location": "Abuja, Nigeria" } }
POST/v1/auth/sign-outSigned in

End the current session.

Request body
{}

Password reset

One email carries two ways to reset: a link (valid 60 minutes) and a 6-digit code (valid 5 minutes). Using either cancels the other, and a reset signs the account out on every device. Unknown emails get the same response and no email.

POST/v1/auth/email-otp/request-password-resetPublic

Email a reset link and a reset code.

Request body
{ "email": "ada@example.com" }
Response 200
{ "success": true }
POST/v1/auth/email-otp/reset-passwordPublic

Reset with the emailed code.

Request body
{ "email": "ada@example.com", "otp": "123456", "password": "new-password" }
Response 200
{ "success": true }
GET/v1/auth/reset-password/{token}?callbackURL=…Public

The emailed link. Validates the token and redirects to the reset page with ?token=… (or ?error=INVALID_TOKEN).

POST/v1/auth/reset-passwordPublic

Reset with the token from the link.

Request body
{ "token": "…", "newPassword": "new-password" }
Response 200
{ "status": true }

Account, sessions and devices

Endpoints for the signed-in user. Each session records its device, IP address and an approximate location.

GET/v1/me/permissionsSigned in

The user's role(s) and permission keys.

Response 200
{ "role": "user", "roles": ["user"], "permissions": [] }
GET/v1/me/tenantsSigned in

Organisations the user belongs to.

Response 200
{ "items": [{ "role": "member", "tenant": { "tid": "unityx", "name": "UnityHUB", … } }] }
POST/v1/auth/update-userSigned in

Update the display name or image.

Request body
{ "name": "Ada O." }
POST/v1/auth/change-passwordSigned in

Change password.

Request body
{ "currentPassword": "…", "newPassword": "…", "revokeOtherSessions": true }
GET/v1/auth/list-sessionsSigned in

Active sessions with device, IP and location.

POST/v1/auth/revoke-sessionSigned in

Sign one device out.

Request body
{ "token": "<session token from list-sessions>" }
POST/v1/auth/revoke-other-sessionsSigned in

Sign out every other device.

Request body
{}
GET/v1/auth/list-accountsSigned in

Linked sign-in methods (credential, google, …).

POST/v1/auth/link-socialSigned in

Link a social provider to the signed-in account. Redirect the browser to the returned url.

Request body
{ "provider": "google", "callbackURL": "https://your.app/account", "errorCallbackURL": "https://your.app/account" }
Response 200
{ "url": "https://accounts.google.com/…", "redirect": true }
POST/v1/auth/unlink-accountSigned in

Unlink a provider (not the last sign-in method).

Request body
{ "accountId": "acc_… (id from list-accounts)" }

Tenants and API keys

Create and manage organisations (tenants) and their API keys, as in the developer console. Owners and admins of a tenant can edit it and manage keys; only the owner can delete it. The default tenant (UnityHUB, unityx) cannot be deleted. Writes must come from a trusted Origin.

GET/v1/me/tenantsSigned in

Tenants you belong to, with your role.

POST/v1/tenantsSigned in

Create a tenant. You become its owner. Requires a verified email; up to 10 per user.

  • 409 tid_taken or tid_reserved; 400 invalid_tid.
Request body
{
  "name": "Acme Ventures",
  "tid": "acme-ventures",        // optional, permanent; derived from name if omitted
  "description": "…",
  "url": "https://acme.example"
}
Response 200
{ "id": "org_…", "tid": "acme-ventures", "name": "Acme Ventures", "status": "active", … }   // 201
GET/v1/tenants/{id}Signed in

A tenant with your role and counts.

Response 200
{ "id": "org_…", "tid": "…", "myRole": "owner", "isDefault": false, "counts": { "members": 1, "apiKeys": 0 } }
PATCH/v1/tenants/{id}Signed in

Update name, description, url, contactEmail, contactPhone or logoUrl (owner or admin).

Request body
{ "name": "Acme Ventures Ltd", "url": "https://acme.example" }
DELETE/v1/tenants/{id}Signed in

Delete a tenant with its memberships and API keys (owner only). Answers 204.

GET/v1/tenants/{id}/membersSigned in

Members and their roles.

GET/v1/tenants/{id}/api-keysSigned in

API keys (name, prefix, dates). Secrets are never returned.

POST/v1/tenants/{id}/api-keysSigned in

Create an API key. The full key is in this response only; store it immediately.

Request body
{ "name": "Production server", "expiresInDays": 365 }   // expiresInDays optional
Response 200
{ "key": "unx_live_…", "apiKey": { "id": "key_…", "prefix": "unx_live_ab12cd", … } }   // 201
DELETE/v1/tenants/{id}/api-keys/{keyId}Signed in

Revoke a key immediately.

GET/v1/integration/whoamiPublic

For integrations: authenticate with a tenant API key and get the tenant it acts for.

  • Send the key as `Authorization: Bearer unx_live_…` or `X-API-Key: unx_live_…`. Revoked, expired or unknown keys get 401.
Response 200
{ "tenant": { "id": "org_…", "tid": "acme-ventures", "name": "Acme Ventures", "status": "active" }, "apiKey": { "id": "key_…", "name": "…", "prefix": "…" } }

Admin

User management for support, admin and super-admin roles. Only a super-admin can grant or remove admin roles or act on an admin account, and the last super-admin can never be demoted. Every action is audited, including refused ones.

GET/v1/auth/admin/list-users?searchValue=&limit=25&offset=0user:list

Search and page through users.

Response 200
{ "users": [ … ], "total": 42 }
POST/v1/auth/admin/set-roleuser:set-role

Change a user's role.

Request body
{ "userId": "usr_…", "role": "support" }
POST/v1/auth/admin/ban-useruser:ban

Ban a user and end their sessions.

Request body
{ "userId": "usr_…", "banReason": "spam" }
POST/v1/auth/admin/unban-useruser:ban

Lift a ban.

Request body
{ "userId": "usr_…" }
POST/v1/auth/admin/revoke-user-sessionssession:revoke

Sign a user out everywhere.

Request body
{ "userId": "usr_…" }
POST/v1/auth/admin/remove-useruser:delete

Delete a user.

Request body
{ "userId": "usr_…" }
GET/v1/admin/audit-logs?action=&actorUserId=&targetId=&before=&limit=50audit:read

Audit log, newest first. Page with nextCursor → before.

Response 200
{ "items": [{ "id": "aud_…", "action": "admin.role_change.success", … }], "nextCursor": "aud_…" }

Roles and permissions

Permissions are resource:action keys. Read the current user's with GET /v1/me/permissions. No role can impersonate users.

RoleWho it is forPermissions
userDefault for every account. Manages their own profile, sign-in methods and devices.None beyond their own account
supportLooks up users and sessions, reads the audit log, views tenants.
user:listuser:getsession:listaudit:readtenant:read
adminManages users, sessions and tenants.
user:createuser:listuser:getuser:updateuser:set-roleuser:banuser:deleteuser:set-passworduser:set-emailsession:listsession:revokesession:deleteaudit:readtenant:readtenant:manage
super-adminEverything admin can, plus granting admin roles and managing admin accounts.
All admin permissions

Tenants

A tenant is an organisation with an owner and members. The default tenant is UnityHUB (tid unityx): every UNX account joins it as a member when it is created, so apps can rely on at least one membership per user.

GET /v1/me/tenants
{
  "items": [
    {
      "role": "member",
      "joinedAt": "2026-09-26T08:00:00.000Z",
      "tenant": { "id": "org_…", "tid": "unityx", "slug": "unityx", "name": "UnityHUB", "status": "active" }
    }
  ]
}

Social sign-in

Google, Microsoft, Apple, GitHub, Facebook, LinkedIn and X are supported. A provider is live once its OAuth client is configured; GET /v1/providers lists those that are. The callback URL registered with each provider is:

OAuth redirect URI
https://auth.unx.ng/v1/auth/callback/{provider}   # e.g. …/callback/google

Account linking. When a social sign-in uses the same email as an existing UNX account, it links to that account automatically, but only if the provider confirms the email is verified and the UNX account's email is verified. Otherwise the user is sent back with ?error=account_not_linked. Signed-in users can also link or unlink providers with POST /v1/auth/link-social and POST /v1/auth/unlink-account.

Errors and limits

Authentication routes return { "code", "message" }. Platform routes under /v1 return { "error": { "code", "message", "requestId", "details" } }; quote requestId (also in the X-Request-Id header) when reporting a problem.

StatusCodeMeaning
400BAD_REQUEST / invalid_queryThe body or query failed validation.
401UNAUTHORIZED / INVALID_EMAIL_OR_PASSWORDNo session, or wrong credentials.
403EMAIL_NOT_VERIFIED / forbiddenVerify the email first, or the role lacks the permission.
404not_foundUnknown route.
429TOO_MANY_REQUESTSRate limit reached: 100 requests a minute per IP, 3 code emails a minute.