Base URL
https://api.wapka.org/v1
All requests to the REST API use this base URL. The site is determined from your authentication token — you do not pass a site ID in the URL.
Authentication
Every /v1/* endpoint requires authentication. Choose one method:
Bearer JWT (recommended)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Obtain a JWT via Authentication. The token contains your site ID, user ID, permission level, and scopes. It is cryptographically signed and valid for 24 hours.
API Key
Authorization: wpk_live_xxxxxxxxxxxxxxxx
API keys are prefixed with wpk_. Create and manage them via POST /v1/api-keys. Use API keys for external integrations and server-to-server calls where JWT refresh is impractical.
JWT structure
A decoded Wapka JWT contains these claims:
| Claim | Description |
|---|---|
iss |
Issuer — always wapka-api |
sub |
Subject — site:{site_id} |
site_id |
Your site's numeric ID |
user_id |
Authenticated user's ID (0 for guest tokens) |
level |
Permission level (0–10) |
persona |
Role label — public, user, moderator, admin, superadmin |
scope |
* for full access, public:read for guest tokens |
iat |
Issued-at timestamp |
exp |
Expiration timestamp |
Permission levels (personas)
| Level | Persona | Capabilities |
|---|---|---|
| 0 | public | Read public fields only |
| 1–5 | user | Read/write own content |
| 6–7 | moderator | Edit/delete content below own level |
| 8–9 | admin | Full CRUD, manage users |
| 10 | superadmin | Everything including API keys and firewall |
Response format
Every API response is JSON and follows one of three envelopes:
Collection response
{
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"totalItems": 142,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
},
"meta": {
"requestId": "req_a1b2c3",
"timestamp": "2026-05-23T12:00:00Z"
}
}
Single resource response
{
"data": { ... },
"meta": {
"requestId": "req_a1b2c3",
"timestamp": "2026-05-23T12:00:00Z"
}
}
Error response
{
"error": {
"code": "not_found",
"message": "Resource not found",
"details": null
},
"meta": {
"requestId": "req_a1b2c3",
"timestamp": "2026-05-23T12:00:00Z"
}
}
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request |
Bad request — check your parameters |
| 400 | invalid_site |
Site context is missing or invalid |
| 401 | unauthorized |
Missing or invalid credentials |
| 401 | expired |
Token has expired — get a new one |
| 403 | forbidden |
Insufficient permission level |
| 403 | jwt_disabled |
JWT auth is disabled for this site |
| 404 | not_found |
Resource does not exist |
| 422 | validation_error |
Input failed validation |
| 429 | rate_limited |
Too many requests — slow down |
| 500 | internal_error |
Something went wrong on the server |
Pagination
All list endpoints (GET /v1/users, GET /v1/posts, etc.) support pagination via query parameters:
| Parameter | Default | Description |
|---|---|---|
page |
1 | Page number (1-based) |
limit |
20 | Items per page (max 100) |
The response includes a pagination object so you know if there are more pages.
Rate limiting
The API enforces per-IP rate limits. When you exceed the limit, you receive a 429 response with error.code = "rate_limited". Implement exponential backoff in your client.
Sorting
List endpoints accept a sort parameter. Use a field name to sort ascending, or prefix with - to sort descending:
GET /v1/posts?sort=-created_at # newest first
GET /v1/users?sort=username # alphabetical
Lua API wrapper
The same REST API is available inside your Lua scripts via the api global. No authentication headers needed — the current request context is used automatically.
local users = api.users:list({ page = 1, limit = 10 })
local post = api.posts:get(42)
Next: Authentication — how to obtain tokens and manage API keys.