Keep your own app's data in Coritan
Store the settings, rules and notes of an app you build on the Organization API as JSON records, with versions, expiry and a feed of changes.
An app you build on the Organization API, such as a staff console, a dashboard or a script, can keep its own small records in Coritan instead of running a database: saved views, alert rules, notes, settings, or a lease that says which copy of the app runs a scheduled job. Each record is one JSON value under a key, inside an app name you choose. Every member of the organization can read every record, and each record says which roles may change it.
Before you begin
Section titled Before you begin- Call the routes with a member's access token, sent as
Authorization: Bearer $CORITAN_TOKEN. Authentication says how to get one. An organization API key is refused with403and the errorpeople_only, because an app writes as the member who uses it. - Pick an app name: 2 to 40 lowercase letters, digits and hyphens, starting with a letter, such as
console. The first write makes it. An organization keeps data for at most 20 app names, and a name keeps its place once used. - Pick your keys: 1 to 160 lowercase letters, digits and the characters
._:@/-, starting with a letter or digit. Slashes group keys, as inviews/42.
Write a record
Section titled Write a recordSend the value in value. Any JSON value works, up to 256 KB once encoded.
curl -X PUT https://api.coritan.com/api/v1/orgs/example-store/app-data/console/records/views/42 \
-H "Authorization: Bearer $CORITAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": {"name": "Big orders", "filters": {"total_min": 10000}}}'
The answer is 201 when the write created the record and 200 when it changed one:
{
"record": {
"key": "views/42",
"value": {"name": "Big orders", "filters": {"total_min": 10000}},
"version": 1,
"change": 18,
"write_role": "admin",
"deleted": false,
"expires_at": null,
"created_at": "2026-10-06T14:00:00Z",
"updated_at": "2026-10-06T14:00:00Z",
"updated_by": {"type": "staff", "id": 1042, "role": "admin"}
}
}
version starts at 1 and goes up by one with every change. change is the app's change number for this write, which the feed of changes uses. updated_by names the member who changed the record last and their role at the time.
Read records
Section titled Read recordsRead one record with GET /api/v1/orgs/{org_slug}/app-data/{app}/records/{key}. A key that holds no record, or holds one that was deleted or has expired, answers 404 with the error record_not_found.
List an app's records in key order with GET /api/v1/orgs/{org_slug}/app-data/{app}/records:
prefixkeeps the keys that start with it, such asviews/.limitis how many come in a page, 100 unless you ask for up to 500.afterstarts the page after a key. Pass thenext_afterof the page before, until it isnull.values=falseleaves the values out, for a list of keys and versions.
GET /api/v1/orgs/{org_slug}/app-data lists every app name of the organization, with how many records each holds (records), the size of their values in bytes (bytes) and its last change number (last_change), and the limits below.
Write without overwriting another change
Section titled Write without overwriting another changeTwo people, or two copies of your app, can change the same record at once. To make sure a write applies only to the version you read, send that version:
curl -X PUT https://api.coritan.com/api/v1/orgs/example-store/app-data/console/records/views/42 \
-H "Authorization: Bearer $CORITAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": {"name": "Big orders", "filters": {"total_min": 20000}}, "version": 1}'
When the record has moved on, the write changes nothing and answers 409 with the error version_conflict and the record's current_version. Read the record again, apply your change to what it holds now, and write with the new version. Send "version": 0 to write only if the key holds no record. Without version, a write replaces whatever is there.
Choose who may change a record
Section titled Choose who may change a recordwrite_role is the lowest role that may change or delete a record. A new record gets your own role unless you send another, and a change keeps the record's write_role unless it sends one. You can set it at most to your own role, so a record an admin locks with "write_role": "billing" cannot be changed by Tier 1 support, and a Tier 1 member cannot make a record that claims a higher role wrote it. The roles, lowest first, are readonly, support_tier1, support_tier2, support_tier3, billing, admin and owner, where admin and owner rank the same. Organization roles and permissions says what each one does elsewhere.
A write below a record's write_role answers 403 with the error role_required.
Make a lease or a one-time claim
Section titled Make a lease or a one-time claimexpires_in gives a record an end, in seconds, from 10 seconds up to 366 days. Once its expires_at has passed, the record reads as absent and its key can be written again, even with "version": 0. Each write sets expires_in again, and a write without it keeps the record for good.
Together they make a lease. Each copy of your app tries PUT with "version": 0 and "expires_in": 60 on a key such as lease. The copy that gets 201 holds it, and renews it before it runs out by writing again with the version it holds. The others get 409 and try again after the lease has expired. A claim works the same way: the first copy to create claims/digest/2026-10-06 with "version": 0 sends that day's digest, and every other copy gets 409.
Follow changes
Section titled Follow changesGET /api/v1/orgs/{org_slug}/app-data/{app}/changes lists every record that changed after a change number, in the order the changes were made, each as it is now. Start with after=0 for everything, then pass the answer's next_after back as after while more is true. Later, ask again with the last next_after for what changed since. Changes are numbered in the order they are saved, so none is skipped. prefix and limit (up to 500) work as on the list.
A deleted record comes in the changes with "deleted": true and a null value for 30 days after it was deleted, so a copy of your data can drop it too. An expired record comes as it was, so compare its expires_at with the time.
Delete a record
Section titled Delete a recordcurl -X DELETE https://api.coritan.com/api/v1/orgs/example-store/app-data/console/records/views/42 \
-H "Authorization: Bearer $CORITAN_TOKEN"
The answer is {"deleted": true, "change": 19}. The key can be written again at once. Add ?version=2 to delete only if the record is at that version, which answers 409 with version_conflict otherwise. A delete below the record's write_role answers 403, and a key with no record answers 404.
Limits
Section titled Limits| What | Limit |
|---|---|
| App names in an organization | 20 |
| Records in an app | 20,000 |
| Size of an app's values | 64 MB |
| Size of one value, as JSON | 256 KB |
| Writes and deletes by one member | 300 a minute |
A write past a limit answers 409 with the error limit_reached and the limit it reached (apps, records or bytes), or 413 with value_too_large for one value. An expired record counts until a later write in the same app clears it away. A value must be JSON, so NaN and Infinity answer 422 with invalid_value.
In the audit log
Section titled In the audit logCreating a record and deleting one are in the organization's audit log as app_data.created and app_data.deleted, under the member who did it. A change to an existing record is not, since apps change their records often; the record's updated_by names who changed it last.
Next steps
Section titled Next steps- Organization roles and permissions: the roles a
write_rolenames. - Read the organization audit log: where creating and deleting records shows up.
- Rate limits: what applies to every request beside the limit on writes.
API operations on this page
| Method | Path | What it does |
|---|---|---|
GET | /api/v1/orgs/{org_slug}/app-data | List apps |
GET | /api/v1/orgs/{org_slug}/app-data/{app}/changes | List changes |
GET | /api/v1/orgs/{org_slug}/app-data/{app}/records | The app's records in key order, without deleted or expired ones |
GET | /api/v1/orgs/{org_slug}/app-data/{app}/records/{key} | One record |
PUT | /api/v1/orgs/{org_slug}/app-data/{app}/records/{key} | Writes the record: 201 when it creates it, 200 when it changes it |
DELETE | /api/v1/orgs/{org_slug}/app-data/{app}/records/{key} | Deletes the record |