API Reference
RESTful API for YeboID authentication and identity management.
Base URLs
| Environment | URL |
|---|---|
| Production | https://api.yeboid.com |
| Staging | https://api-staging.yeboid.com |
Authentication
Public Endpoints
No authentication required:
POST /auth/*(except logout)GET /users/@:handleGET /users/handle/checkGET /health
Protected Endpoints
Include the access token in the Authorization header:
Authorization: Bearer <access_token>Request Format
Content-Type: application/json
Authorization: Bearer <token>
X-Request-ID: <uuid> # Optional, for tracingResponse Format
Success
{
"success": true,
"data": { ... }
}Error
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"details": { ... }
}
}Health
GET /health
Check API status.
Response:
{
"status": "ok",
"version": "1.0.0",
"timestamp": "2026-03-18T20:00:00Z"
}Authentication
POST /auth/otp/send
Send OTP to phone number.
Request:
{
"phone": "+26878422613",
"purpose": "signup"
}| Field | Type | Required | Description |
|---|---|---|---|
phone | string | Yes | E.164 format |
purpose | string | Yes | signup or pin_reset |
Response:
{
"success": true,
"data": {
"expires_in": 300,
"message": "OTP sent to +268****613"
}
}POST /auth/otp/verify
Verify OTP code.
Request:
{
"phone": "+26878422613",
"code": "123456",
"purpose": "signup"
}Response:
{
"success": true,
"data": {
"verified": true,
"temp_token": "eyJ...",
"expires_in": 600
}
}POST /auth/signup
Create account after OTP verification.
Request:
{
"temp_token": "eyJ...",
"pin": "1234",
"handle": "laslie",
"name": "Laslie Georges Jr."
}| Field | Type | Required | Description |
|---|---|---|---|
temp_token | string | Yes | From OTP verify |
pin | string | Yes | 4-6 digits |
handle | string | Yes | 3-30 chars, lowercase |
name | string | No | Display name |
Response:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"phone": "+26878422613",
"handle": "laslie",
"name": "Laslie Georges Jr.",
"avatar_url": null,
"kyc_status": "none",
"created_at": "2026-03-18T20:00:00Z"
},
"access_token": "eyJ...",
"refresh_token": "abc123...",
"expires_in": 900
}
}POST /auth/signin
Sign in with phone and PIN.
Request:
{
"phone": "+26878422613",
"pin": "1234"
}Response:
{
"success": true,
"data": {
"user": { ... },
"access_token": "eyJ...",
"refresh_token": "abc123...",
"expires_in": 900
}
}POST /auth/refresh
Refresh access token.
Request:
{
"refresh_token": "abc123..."
}Response:
{
"success": true,
"data": {
"access_token": "eyJ...",
"refresh_token": "def456...",
"expires_in": 900
}
}Token Rotation
Refresh tokens are rotated on each use. The old token is invalidated.
POST /auth/logout
Revoke refresh token.
Headers: Authorization: Bearer <access_token>
Request:
{
"refresh_token": "abc123..."
}Users
GET /users/me
Get authenticated user's profile.
Headers: Authorization: Bearer <access_token>
Response:
{
"success": true,
"data": {
"id": "uuid",
"phone": "+26878422613",
"phone_verified": true,
"handle": "laslie",
"name": "Laslie Georges Jr.",
"avatar_url": "https://...",
"bio": "Building the future of Africa",
"country": "SZ",
"language": "en",
"kyc_status": "verified",
"kyc_country": "SZ",
"kyc_verified_at": "2026-03-18T20:00:00Z",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-18T20:00:00Z"
}
}PATCH /users/me
Update user profile.
Headers: Authorization: Bearer <access_token>
Request:
{
"name": "Laslie G.",
"bio": "CEO @ Omevision",
"avatar_url": "https://...",
"language": "en"
}All fields optional. Only include fields to update.
GET /users/@:handle
Get public profile by handle.
Response:
{
"success": true,
"data": {
"id": "uuid",
"handle": "laslie",
"name": "Laslie Georges Jr.",
"avatar_url": "https://...",
"bio": "Building the future of Africa",
"kyc_status": "verified",
"created_at": "2026-03-01T10:00:00Z"
}
}Privacy
Phone, country, and language are NOT included in public profiles.
GET /users/handle/check
Check handle availability.
Query: ?handle=<handle>
Response:
{
"success": true,
"data": {
"handle": "newhandle",
"available": true
}
}DELETE /users/me
Delete account permanently.
Headers: Authorization: Bearer <access_token>
Request:
{
"pin": "1234",
"confirmation": "DELETE MY ACCOUNT"
}Irreversible
This action is irreversible. Data is hard-deleted after 30 days.
Sessions
GET /sessions
List active sessions.
Headers: Authorization: Bearer <access_token>
Response:
{
"success": true,
"data": {
"sessions": [
{
"id": "uuid",
"device_name": "iPhone 15 Pro",
"platform": "ios",
"ip_address": "102.xxx.xxx.xxx",
"last_used_at": "2026-03-18T20:00:00Z",
"created_at": "2026-03-15T10:00:00Z",
"current": true
}
],
"total": 1
}
}DELETE /sessions/:id
Revoke a session.
Headers: Authorization: Bearer <access_token>
KYC
GET /kyc/status
Get KYC verification status.
Headers: Authorization: Bearer <access_token>
Response:
{
"success": true,
"data": {
"status": "verified",
"verified_at": "2026-03-18T20:00:00Z",
"level": "standard",
"documents": ["id_card"]
}
}POST /kyc/initiate
Start KYC verification.
Headers: Authorization: Bearer <access_token>
Response:
{
"success": true,
"data": {
"verification_url": "https://verify.yeboid.com/session/abc123",
"expires_in": 1800
}
}Redirect user to verification_url to complete KYC.
Error Codes
| Code | HTTP | Description |
|---|---|---|
INVALID_REQUEST | 400 | Malformed request |
INVALID_PHONE | 400 | Invalid phone format |
INVALID_PIN | 400 | PIN doesn't meet requirements |
INVALID_HANDLE | 400 | Invalid handle format |
INVALID_OTP | 400 | Wrong OTP code |
OTP_EXPIRED | 400 | OTP has expired |
INVALID_CREDENTIALS | 401 | Wrong phone or PIN |
INVALID_TOKEN | 401 | Access token invalid |
TOKEN_EXPIRED | 401 | Access token expired |
INVALID_REFRESH_TOKEN | 401 | Refresh token invalid |
ACCOUNT_LOCKED | 403 | Account temporarily locked |
FORBIDDEN | 403 | Not allowed |
NOT_FOUND | 404 | Resource not found |
PHONE_NOT_FOUND | 404 | Phone not registered |
HANDLE_TAKEN | 409 | Handle already in use |
HANDLE_RESERVED | 409 | Handle is reserved |
PHONE_EXISTS | 409 | Phone already registered |
HANDLE_COOLDOWN | 429 | Must wait between handle changes |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
POST /auth/otp/send | 3 | 1 hour |
POST /auth/signin | 5 | 15 minutes |
POST /auth/otp/verify | 5 | per code |
GET /users/handle/check | 30 | 1 minute |
| All other endpoints | 100 | 1 minute |
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1710770000SDKs
- Flutter SDK — Official Flutter package
- More SDKs coming soon
API Version: 1.0