# 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 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: ```json { "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 ```json { "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:** ```json { "email": "user@acme.com", "password": "secret123" } ``` **Response (200):** ```json { "access": "eyJ...", "refresh": "eyJ...", "token_type": "bearer", "expires_in": 900 } ``` ### POST `/auth/refresh` Rotate access token. **Request:** ```json { "refresh": "eyJ..." } ``` **Response (200):** ```json { "access": "eyJ...", "refresh": "eyJ...", // rotated "token_type": "bearer", "expires_in": 900 } ``` ### POST `/auth/logout` Revoke session + refresh token. **Request:** ```json { "refresh": "eyJ..." // optional; if omitted, revokes current session only } ``` **Response (204):** No content. --- ## Users ### GET `/users/me` Current authenticated user. **Response (200):** ```json { "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):** ```json { "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:** ```json { "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):** ```json { "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:** ```json { "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):** ```json { "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:** ```json { "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):** ```json { "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):** ```json { "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_failed` - `logout`, `token_refreshed` - `session_revoked`, `password_changed` - `mfa_changed`, `mfa_enrolled`, `mfa_removed` - `suspicious_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=&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= redirect_uri=https://bermooda.com/callback client_id= client_secret= code_verifier= # required only if code_challenge was used ``` **Grant: `refresh_token`** ``` grant_type=refresh_token refresh_token= client_secret= ``` **Response (200):** ```json { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 900, "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. ```json { "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)