11 KiB
API Reference
Base URL
Versioned identity API:
https://id.example.com/v1/
OAuth 2.0 / OIDC protocol endpoints live at the API root (not under /v1/):
https://id.example.com/oauth/authorize
https://id.example.com/oauth/token
https://id.example.com/oauth/jwks
https://id.example.com/.well-known/openid-configuration
Authentication
All endpoints (except auth, oauth, discovery) require:
Authorization: Bearer <access_token>
Access tokens are JWTs signed with HS256 (HMAC) using the platform
JWT_SIGNING_KEY. They are verified by a shared secret, not via JWKS — the
JWKS endpoint (/oauth/jwks) currently returns an empty key set.
Pagination
List endpoints return:
{
"count": 123,
"next": "https://id.example.com/api/v1/endpoint?page=2",
"previous": null,
"results": [...]
}
Default page size: 20. Override with ?page_size=50 (max 100).
Filtering & Search
| Param | Example |
|---|---|
search |
?search=john (icontains on name/email) |
ordering |
?ordering=-created_at (prefix - for desc) |
field=value |
?status=active (exact match) |
Error Format
{
"detail": "Human-readable message",
"code": "ERROR_CODE",
"fields": {
"email": ["Enter a valid email."]
}
}
HTTP status codes: 200, 201, 400, 401, 403, 404, 429, 500.
Authentication
POST /auth/login
Obtain token pair.
Request:
{
"email": "user@acme.com",
"password": "secret123"
}
Response (200):
{
"access": "eyJ...",
"refresh": "eyJ...",
"token_type": "bearer",
"expires_in": 900
}
POST /auth/refresh
Rotate access token.
Request:
{
"refresh": "eyJ..."
}
Response (200):
{
"access": "eyJ...",
"refresh": "eyJ...", // rotated
"token_type": "bearer",
"expires_in": 900
}
POST /auth/logout
Revoke session + refresh token.
Request:
{
"refresh": "eyJ..." // optional; if omitted, revokes current session only
}
Response (204): No content.
Users
GET /users/me
Current authenticated user.
Response (200):
{
"id": "uuid",
"email": "user@acme.com",
"phone": "+989123456789",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_staff": false,
"date_joined": "2024-01-15T10:30:00Z",
"last_login": "2024-01-20T08:15:00Z",
"organizations": [
{ "id": "uuid", "name": "Acme Corp", "role": "owner" }
]
}
GET /users/{id}
Admin only. User detail by UUID.
Organizations
GET /organizations
List organizations user belongs to.
Response (200):
{
"count": 2,
"results": [
{
"id": "uuid",
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Main organization",
"is_active": true,
"created_at": "2024-01-10T12:00:00Z",
"owner": { "id": "uuid", "email": "owner@acme.com" },
"member_count": 15,
"user_role": "owner"
}
]
}
POST /organizations
Create organization (caller becomes owner).
Request:
{
"name": "New Company",
"description": "Optional description"
}
Response (201): Organization object.
GET /organizations/{id}
Organization detail.
PATCH /organizations/{id}
Update (owner/admin only).
DELETE /organizations/{id}
Delete (owner only).
Memberships
GET /organizations/{org_id}/memberships
List members of an organization.
Response (200):
{
"count": 15,
"results": [
{
"id": "uuid",
"user": { "id": "uuid", "email": "user@acme.com", "name": "John Doe" },
"role": "member",
"status": "active",
"joined_at": "2024-01-15T10:30:00Z",
"invited_by": { "id": "uuid", "email": "owner@acme.com" }
}
]
}
POST /organizations/{org_id}/memberships
Invite user (owner/admin).
Request:
{
"email": "new@user.com",
"role": "member"
}
PATCH /organizations/{org_id}/memberships/{membership_id}
Update role/status (owner/admin).
DELETE /organizations/{org_id}/memberships/{membership_id}
Remove member (owner) or leave (self).
Applications (OAuth Clients)
GET /applications
List applications (admin).
Response (200):
{
"count": 3,
"results": [
{
"id": "uuid",
"name": "Bermooda",
"client_id": "bermooda-client-id",
"client_type": "confidential",
"redirect_uris": ["https://bermooda.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"scopes": ["openid", "profile", "email", "org:read"],
"is_active": true,
"created_at": "2024-01-10T12:00:00Z"
}
]
}
POST /applications
Register new application.
Request:
{
"name": "My App",
"client_type": "confidential",
"redirect_uris": ["https://app.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scopes": ["openid", "profile", "email"]
}
Response (201): Application with client_secret (shown once).
GET /applications/{id}
PATCH /applications/{id}
DELETE /applications/{id}
POST /applications/{id}/rotate-secret
Rotate client secret.
Sessions
GET /sessions
List current user's active sessions.
Response (200):
{
"count": 3,
"results": [
{
"id": "uuid",
"device_name": "Chrome on macOS",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"type": "browser",
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"last_activity": "2024-01-20T08:15:00Z",
"current": true
}
]
}
DELETE /sessions/{id}
Revoke session (also revokes its refresh token).
Response (204): No content.
Security Events
GET /events
List security events (admin, or user's own events).
Query params: user_id, event_type, severity, date_from, date_to
Response (200):
{
"count": 50,
"results": [
{
"id": "uuid",
"user": { "id": "uuid", "email": "user@acme.com" },
"event_type": "login_success",
"severity": "low",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"metadata": { "session_id": "uuid" },
"created_at": "2024-01-20T08:15:00Z"
}
]
}
Event Types:
login_success,login_failedlogout,token_refreshedsession_revoked,password_changedmfa_changed,mfa_enrolled,mfa_removedsuspicious_activity,brute_force_detected
Severity: low, medium, high, critical
OAuth 2.0 / OIDC
Authorization Code + Refresh Token grants with PKCE support.
GET /oauth/authorize
Start the authorization code flow.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
client_id |
yes | Application (OAuth client) identifier |
response_type |
yes | Must be code |
redirect_uri |
yes | Must match a registered redirect URI for the client |
scope |
yes | Space-delimited scopes (e.g. openid profile) |
state |
no | Opaque value echoed back to the client |
nonce |
no | Binds authorisation to the token |
code_challenge |
no | PKCE code challenge |
code_challenge_method |
no | S256 (default plain) |
token |
no | Bearer JWT access token may be passed as a query parameter instead of a header |
If the user is not authenticated (or inactive), responds 401 with a login_url.
Otherwise redirects (302) to the registered redirect_uri with code + optional state:
https://bermooda.com/callback?code=<raw_code>&state=abc123
On validation failure (invalid client_id, redirect_uri mismatch, unsupported
response_type), responds 400 with error (unauthorized_client,
invalid_request, unsupported_response_type, invalid_scope).
POST /oauth/token
Exchange an authorization code for tokens, or refresh an access token.
Content-Type: application/x-www-form-urlencoded
Grant: authorization_code
grant_type=authorization_code
code=<raw_code>
redirect_uri=https://bermooda.com/callback
client_id=<client_id>
client_secret=<client_secret>
code_verifier=<pkce_verifier> # required only if code_challenge was used
Grant: refresh_token
grant_type=refresh_token
refresh_token=<refresh_token>
client_secret=<client_secret>
Response (200):
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "<raw_refresh_token>", // rotated on refresh
"scope": "openid profile"
}
Authorization codes are single-use. Refresh tokens are rotated on each use and the previous refresh token is revoked.
Errors: 400 invalid_client, 400 invalid_grant, 400 invalid_request,
401 invalid_client, 400 unsupported_grant_type.
GET /oauth/jwks
Returns the JSON Web Key Set. Currently returns an empty key set {"keys": []}
because tokens are signed with HMAC (HS256), not an asymmetric key pair.
GET /oauth/scopes/
Lists the non-system OAuth scopes available to applications.
OIDC Discovery
GET /.well-known/openid-configuration
Standard OpenID Provider configuration.
{
"issuer": "https://id.example.com",
"authorization_endpoint": "https://id.example.com/oauth/authorize",
"token_endpoint": "https://id.example.com/oauth/token",
"jwks_uri": "https://id.example.com/oauth/jwks",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["public"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"id_token_signing_alg_values_supported": ["HS256"],
"claims_supported": ["sub", "name", "email", "email_verified", "preferred_username", "locale", "picture"],
"code_challenge_methods_supported": ["S256", "plain"]
}
Admin Endpoints
GET /admin/users/ — List all users (staff)
GET /admin/organizations/ — All organizations
GET /admin/applications/ — All OAuth clients
GET /admin/security/events/ — Full audit log
Access: Django admin or staff API token.
Rate Limits
| Scope | Limit |
|---|---|
| Per-user (authenticated) | 100 req/min |
| Per-IP (unauthenticated) | 20 req/min |
/auth/login |
5/min/IP, 10/min/user |
/auth/refresh |
20/min/user |
Headers on 429:
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
SDKs / Clients
| Language | Package |
|---|---|
| Python | pip install identity-platform-sdk (planned) |
| JavaScript | npm install @identity-platform/client (planned) |
| Go | go get github.com/identity-platform/go-sdk (planned) |
Until SDKs exist, use standard HTTP clients with the endpoints above.
Changelog
| Version | Date | Changes |
|---|---|---|
| v1.0.0 | 2024-01 | Initial release: auth, users, orgs, memberships, apps, sessions, events, OIDC discovery |
Support
- API Issues: Internal ticket system
- Security:
security@identity-platform.internal - Docs:
/api/docs/(Swagger UI)