REST API
The interface talks to Qorekit only through its JSON API, under /api/v1, so everything the interface does, the API can do.
Signing in
For now the API uses the same session as the interface: sign in with POST /api/v1/auth/login and send the session cookie and the X-XSRF-TOKEN header with every request, as a browser does. Tokens for other programs are planned.
Conventions
- Requests and responses are JSON. Send
Accept: application/json. - Responses wrap their content in
data; lists addmeta. - Keys, not ids, name modules, entities, fields and choice lists in addresses:
/api/v1/records/customers. - Dates and times are ISO 8601. Times are returned in UTC (
2026-11-13T07:00:00Z); a time sent without a zone is refused. - Decimal numbers and money are strings (
"1250.50"), so no precision is lost. - Errors follow RFC 9457 problem details, as
application/problem+json.
Records
| Request | Does |
|---|---|
GET /records/{entity} | A page of records |
POST /records/{entity} | Adds a record |
GET /records/{entity}/{id} | One record |
PATCH /records/{entity}/{id} | Changes the fields in the body |
DELETE /records/{entity}/{id} | Moves the record to the trash |
POST /records/{entity}/{id}/restore | Brings it back from the trash |
POST /records/{entity}/{id}/reveal | The whole value of a sensitive field: {"field": "iban"} |
Listing
GET /api/v1/records/contacts?page=2&per_page=50&sort=-created_at&search=ada| Parameter | Meaning |
|---|---|
page, per_page | Paging; per_page is 50 by default, 200 at most |
sort | A field key, or -key for descending; also created_at and updated_at |
search | Text to look for in the searchable fields |
search_in | Comma-separated field keys to search instead; _title is the heading |
fields | Comma-separated field keys to return, for smaller responses |
trashed=only | Records in the trash instead |
count=false | Skip counting, for speed |
{
"data": [
{
"id": 42,
"name": "Ada",
"email": "ada@example.com",
"company": { "id": 7, "label": "Acme" },
"tags": [{ "id": 16, "key": "finance", "label": { "en": "Finance", "tr": "Finans" }, "color": null }],
"_meta": {
"title": "Ada",
"version": 3,
"created_at": "2026-11-14T14:04:06Z",
"updated_at": "2026-11-14T15:30:00Z",
"deleted_at": null,
"created_by": 1,
"updated_by": 2,
"created_by_name": "Demo Admin",
"updated_by_name": "Sam Seller"
}
}
],
"meta": { "page": 2, "per_page": 50, "total": 1234, "total_capped": false }
}A search counts up to 1000 matches; beyond that total stops at 1000 and total_capped is true.
Writing
Send field keys and values. Related records and users are written as ids ("company": 7, or [7, 9] when several are allowed) and read as {id, label}. Choices are written by item key (or id) and read as {id, key, label, color}. A field with several values also takes {"add": [...], "remove": [...]}, to change some without sending them all.
_meta.title is the record's heading.
Conflicting edits
Every record has a version. Send it back as If-Match when changing a record:
PATCH /api/v1/records/contacts/42
If-Match: "3"
Content-Type: application/json
{"email": "ada@acme.example"}If someone changed the record in between, the answer is 409 Conflict with the code version_mismatch and the record as it is now in current. Without If-Match, the change is applied whatever the version.
Errors
{
"type": "https://qorekit.com/problems/validation-failed",
"title": "The given data was invalid",
"status": 422,
"detail": "This value is already used by another record.",
"errors": {
"email": [{ "code": "taken", "message": "This value is already used by another record." }]
}
}Each field error has a stable code (required, taken, not_integer, invalid_choice, …) to act on, and an English message to show.
| Status | Meaning |
|---|---|
| 401 | Not signed in |
| 403 | Signed in, but not allowed |
| 404 | No such thing, or one you may not see |
| 409 | A conflicting edit |
| 422 | Invalid input; see errors |
| 429 | Too many requests; wait and try again |
Other endpoints
| Area | Endpoints |
|---|---|
| Signing in | POST auth/login, POST auth/logout, GET/PATCH auth/me, PUT auth/me/password |
| Building | meta/modules, meta/entities, meta/entities/{entity}/fields, meta/choice-lists, meta/field-types, meta/trash |
| Files | POST files (upload), GET files/{file} |
| People | users, users/lookup, roles |
| Installation | settings, system/status, system/report, system/updates |
GET /api/v1/system/status needs no sign-in, and says whether the installation works and which version it runs.