API Contracts
FORGE generates a RESTful API that follows strict contracts for request/response format, authentication, error handling, and pagination. This page is the complete endpoint reference for every route in a generated application.
Base URL
All API endpoints are served under a versioned base path:
https://api.{app-name}.test/api/v1Authentication
Protected endpoints require a valid JWT access token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...TIP
For browser-based applications, FORGE also supports HttpOnly cookie delivery. The token is automatically sent with every request when using cookies -- no Authorization header needed.
Response Format
Every API response follows a consistent envelope structure.
Success Response
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"email": "john@example.com"
}
}Paginated Response
{
"success": true,
"data": [
{ "id": "...", "name": "..." },
{ "id": "...", "name": "..." }
],
"meta": {
"page": 1,
"per_page": 15,
"total": 42,
"last_page": 3
}
}Error Response
{
"success": false,
"message": "Validation failed",
"errors": {
"email": ["The email field is required"],
"password": ["Must be at least 8 characters"]
}
}HTTP Status Codes
| Code | Meaning | When |
|---|---|---|
200 | OK | Successful GET, PUT |
201 | Created | Successful POST (resource created) |
204 | No Content | Successful DELETE |
401 | Unauthorized | Missing or invalid token |
403 | Forbidden | Valid token but insufficient permissions |
404 | Not Found | Resource does not exist |
422 | Unprocessable Entity | Validation errors |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Unexpected server error |
Pagination
All list endpoints accept pagination query parameters:
GET /api/v1/admin/users?page=2&per_page=25| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 15 | Items per page (max 100) |
search | string | -- | Search term (where supported) |
sort | string | created_at | Sort field |
order | string | desc | Sort direction (asc or desc) |
Auth Endpoints
Authentication endpoints are public (no token required) unless noted.
POST /auth/login
Authenticate a user and receive JWT tokens.
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "securepassword"
}{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"email": "user@example.com",
"avatar": null,
"roles": ["user"],
"permissions": []
}
}
}{
"success": false,
"message": "Invalid credentials"
}POST /auth/register
Create a new user account.
POST /api/v1/auth/register
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "securepassword",
"password_confirmation": "securepassword"
}{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Jane Doe",
"email": "jane@example.com",
"roles": ["user"],
"permissions": []
}
}
}POST /auth/refresh
Exchange a valid refresh token for a new token pair.
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900
}
}WARNING
Refresh tokens are single-use. Each call invalidates the previous token and issues a new pair. If a refresh token is reused, all tokens for that user are revoked as a security precaution (refresh token rotation).
POST /auth/logout
Invalidate the current session. Requires authentication.
POST /api/v1/auth/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Response (200):
{
"success": true,
"message": "Logged out successfully"
}POST /auth/password/forgot
Send a password reset link to the user's email.
POST /api/v1/auth/password/forgot
Content-Type: application/json
{
"email": "user@example.com"
}Response (200):
{
"success": true,
"message": "Password reset link sent"
}POST /auth/password/reset
Reset the password using a token from the email.
POST /api/v1/auth/password/reset
Content-Type: application/json
{
"token": "reset-token-from-email",
"password": "newsecurepassword",
"password_confirmation": "newsecurepassword"
}POST /auth/otp/send
Request a one-time password for mobile authentication.
POST /api/v1/auth/otp/send
Content-Type: application/json
{
"mobile": "+966500000000"
}Response (200):
{
"success": true,
"message": "OTP sent successfully"
}POST /auth/otp/verify
Verify the OTP and authenticate the user.
POST /api/v1/auth/otp/verify
Content-Type: application/json
{
"mobile": "+966500000000",
"otp": "123456"
}{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "...",
"name": "...",
"mobile": "+966500000000"
}
}
}Profile Endpoints
All profile endpoints require authentication.
GET /profile
Retrieve the authenticated user's profile.
GET /api/v1/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Response (200):
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"email": "john@example.com",
"mobile": null,
"avatar": "/media/avatars/john.jpg",
"roles": ["user"],
"permissions": [],
"email_verified_at": "2026-01-15T10:30:00Z",
"created_at": "2026-01-01T00:00:00Z"
}
}PUT /profile
Update the authenticated user's profile information.
PUT /api/v1/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"name": "John Smith",
"email": "john.smith@example.com"
}POST /profile/avatar
Upload or replace the user's avatar image.
POST /api/v1/profile/avatar
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: multipart/form-data
avatar: (binary file)PUT /profile/password
Change the authenticated user's password.
PUT /api/v1/profile/password
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"current_password": "oldsecurepassword",
"password": "newsecurepassword",
"password_confirmation": "newsecurepassword"
}Admin Endpoints
All admin endpoints require authentication and the appropriate permission. Requests without the required permission receive a 403 Forbidden response.
Users
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/users | users.view | List all users (paginated) |
POST | /admin/users | users.create | Create a new user |
GET | /admin/users/:id | users.view | Get a single user |
PUT | /admin/users/:id | users.edit | Update a user |
DELETE | /admin/users/:id | users.delete | Soft-delete a user |
GET /api/v1/admin/users?page=1&per_page=15&search=john
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...POST /api/v1/admin/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"name": "New User",
"email": "new@example.com",
"password": "securepassword",
"is_active": true,
"roles": ["user"]
}PUT /api/v1/admin/users/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"name": "Updated Name",
"is_active": false
}Roles
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/roles | roles.view | List all roles (paginated) |
POST | /admin/roles | roles.create | Create a new role |
GET | /admin/roles/:id | roles.view | Get a single role with permissions |
PUT | /admin/roles/:id | roles.edit | Update role and permissions |
DELETE | /admin/roles/:id | roles.delete | Delete a role |
DANGER
System roles (admin, user) cannot be deleted. Attempting to delete a system role returns 403 Forbidden.
Permissions
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/permissions | permissions.view | List all permissions (grouped) |
Permissions are read-only. They are created by seeders and template manifests, not by admin users.
Contents
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/contents | contents.view | List content pages (paginated) |
POST | /admin/contents | contents.create | Create a content page |
GET | /admin/contents/:id | contents.view | Get a single content page |
PUT | /admin/contents/:id | contents.edit | Update a content page |
DELETE | /admin/contents/:id | contents.delete | Soft-delete a content page |
POST /api/v1/admin/contents
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: multipart/form-data
title: About Us
slug: about-us
summary: Learn more about our company
details: <p>Full HTML content here...</p>
seo: {"title":"About Us","description":"Company info"}
translations: {"ar":{"title":"من نحن","summary":"تعرف على شركتنا"}}
is_active: true
publish_at: 2026-02-01T00:00:00Z
featured_image: (binary file, optional){
"success": true,
"data": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"title": "About Us",
"slug": "about-us",
"summary": "Learn more about our company",
"details": "<p>Full HTML content here...</p>",
"seo": { "title": "About Us", "description": "Company info" },
"translations": { "ar": { "title": "من نحن" } },
"is_active": true,
"publish_at": "2026-02-01T00:00:00Z",
"featured_image": {
"id": "...",
"url": "/media/contents/about-hero.jpg"
},
"created_at": "2026-01-27T12:00:00Z"
}
}Menus
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/menus | menus.view | List all menu items (paginated) |
POST | /admin/menus | menus.create | Create a menu item |
GET | /admin/menus/:id | menus.view | Get a single menu item |
PUT | /admin/menus/:id | menus.edit | Update a menu item |
DELETE | /admin/menus/:id | menus.delete | Soft-delete a menu item |
POST /api/v1/admin/menus
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"title": "Home",
"url": "/",
"icon": "home",
"target": "_self",
"visibility": "public",
"parent_id": null,
"sort_order": 1,
"is_active": true,
"translations": {
"ar": { "title": "الرئيسية" }
}
}Lookups
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/lookups | lookups.view | List lookups (filterable by type) |
POST | /admin/lookups | lookups.create | Create a lookup entry |
GET | /admin/lookups/:id | lookups.view | Get a single lookup |
PUT | /admin/lookups/:id | lookups.edit | Update a lookup |
DELETE | /admin/lookups/:id | lookups.delete | Delete a lookup |
GET /api/v1/admin/lookups?type=country&page=1Settings
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/settings | settings.view | Get all settings (grouped) |
PUT | /admin/settings | settings.edit | Update settings (batch) |
PUT /api/v1/admin/settings
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"general.app_name": "My Application",
"general.timezone": "Asia/Riyadh",
"auth.allow_registration": "true"
}Languages
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/languages | settings.view | List all languages |
POST | /admin/languages | settings.edit | Add a language |
PUT | /admin/languages/:id | settings.edit | Update a language |
DELETE | /admin/languages/:id | settings.edit | Remove a language |
Translations
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/translations | translations.view | List translations (by group) |
PUT | /admin/translations | translations.edit | Update translations (batch) |
GET /api/v1/admin/translations?group=authAPI Keys
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/api-keys | api_keys.view | List API keys |
POST | /admin/api-keys | api_keys.create | Create an API key |
DELETE | /admin/api-keys/:id | api_keys.revoke | Revoke an API key |
WARNING
The plain-text API key is only returned once, in the creation response. It is never stored or retrievable afterward. Store it securely immediately after creation.
Audit Logs
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /admin/audit-logs | audit.view | List audit logs (paginated, filterable) |
GET /api/v1/admin/audit-logs?resource_type=user&action=updated&page=1Public Endpoints
Public endpoints require no authentication.
GET /contents/:slug
Retrieve a published content page by its slug. Accepts an optional locale query parameter for translated content.
GET /api/v1/contents/about-us?locale=arResponse (200):
{
"success": true,
"data": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"title": "من نحن",
"slug": "about-us",
"summary": "تعرف على شركتنا",
"details": "<p>محتوى الصفحة...</p>",
"seo": { "title": "من نحن", "description": "..." },
"featured_image": {
"url": "/media/contents/about-hero.jpg"
}
}
}TIP
When a locale parameter is provided, the API merges the requested language's translations over the default language fields. If a translation is missing for a specific field, the default language value is used as a fallback.
GET /menus
Retrieve active navigation menu items as a nested tree. Supports locale and authentication-aware visibility filtering.
GET /api/v1/menus?locale=arResponse (200):
{
"success": true,
"data": [
{
"id": "...",
"title": "الرئيسية",
"url": "/",
"icon": "home",
"target": "_self",
"children": []
},
{
"id": "...",
"title": "من نحن",
"url": "/about-us",
"icon": null,
"target": "_self",
"children": [
{
"id": "...",
"title": "فريقنا",
"url": "/our-team",
"icon": null,
"target": "_self",
"children": []
}
]
}
]
}GET /lookups
Retrieve active lookup entries by type.
GET /api/v1/lookups?type=country&locale=arGET /languages
Retrieve the list of active languages.
GET /api/v1/languagesResponse (200):
{
"success": true,
"data": [
{ "code": "en", "name": "English", "native_name": "English", "direction": "ltr", "is_default": true },
{ "code": "ar", "name": "Arabic", "native_name": "العربية", "direction": "rtl", "is_default": false }
]
}GET /translations
Retrieve UI translations for the frontend.
GET /api/v1/translations?locale=ar&groups=common,auth,web.homeGET /settings/public
Retrieve public-facing settings.
GET /api/v1/settings/publicResponse (200):
{
"success": true,
"data": {
"app_name": "My Application",
"default_language": "en",
"date_format": "YYYY-MM-DD",
"allow_registration": true
}
}Admin Dashboard
GET /admin/stats
Retrieve dashboard statistics. Requires admin.view permission.
GET /api/v1/admin/stats
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Response (200):
{
"success": true,
"data": {
"users_count": 156,
"roles_count": 5,
"contents_count": 23,
"recent_activity": [
{ "action": "created", "resource_type": "user", "created_at": "..." }
]
}
}Error Codes Reference
| Error Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 422 | Request body failed validation |
UNAUTHORIZED | 401 | Missing or expired token |
FORBIDDEN | 403 | Insufficient permissions |
NOT_FOUND | 404 | Resource not found |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Unexpected server error |
See Also
- Authentication -- JWT implementation details
- Authorization -- Permission middleware
- Routes -- Route registration code
- Handlers -- Request handler implementations
- Error Handling -- Error response formatting