Working with Tables
Tables is a database your team uses in Olamma: bases of tables with typed fields, saved views and records. This guide takes you from an empty base to filtered, safely updated records.
How it fits together
| Base | A set of related tables, like Sales, in one project. Project access decides who can see it. |
| Table | Rows of one kind, like Orders. |
| Field | A column with a type, like Total (currency). The type decides what cells accept. |
| View | A saved way to look at a table: its sort, filters and columns. You read records through a view. |
| Record | A row. Its cells are values keyed by field id. |
Ids look like tb_… (base), tt_… (table), tf_… (field), tv_… (view) and tr_… (record). Get them from earlier answers; the examples use tf_status-style names to stay readable.
From an empty base to records
- Create a base, or find one with List bases.
curl -X POST "https://api.olamma.sh/v1/w/your-workspace/bases" \ -H "Authorization: Bearer $OLAMMA_KEY" \ -H "Idempotency-Key: setup-sales-base" \ -H "Content-Type: application/json" \ -d '{ "name": "Sales" }'const response = await fetch(`https://api.olamma.sh/v1/w/your-workspace/bases`, { method: "POST", headers: { Authorization: `Bearer ${process.env.OLAMMA_KEY}`, "Idempotency-Key": "setup-sales-base", "Content-Type": "application/json", }, body: JSON.stringify({ "name": "Sales" }), }); const data = await response.json(); if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os import requests response = requests.post( "https://api.olamma.sh/v1/w/your-workspace/bases", headers={"Authorization": f"Bearer {os.environ['OLAMMA_KEY']}", "Idempotency-Key": "setup-sales-base"}, json={ "name": "Sales" }, timeout=30, ) data = response.json() response.raise_for_status() - Create a table in it. It starts with a Name field and a Grid view; the answer includes both ids.
curl -X POST "https://api.olamma.sh/v1/w/your-workspace/bases/tb_…/tables" \ -H "Authorization: Bearer $OLAMMA_KEY" \ -H "Idempotency-Key: setup-orders-table" \ -H "Content-Type: application/json" \ -d '{ "name": "Orders" }'const response = await fetch(`https://api.olamma.sh/v1/w/your-workspace/bases/tb_…/tables`, { method: "POST", headers: { Authorization: `Bearer ${process.env.OLAMMA_KEY}`, "Idempotency-Key": "setup-orders-table", "Content-Type": "application/json", }, body: JSON.stringify({ "name": "Orders" }), }); const data = await response.json(); if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os import requests response = requests.post( "https://api.olamma.sh/v1/w/your-workspace/bases/tb_…/tables", headers={"Authorization": f"Bearer {os.environ['OLAMMA_KEY']}", "Idempotency-Key": "setup-orders-table"}, json={ "name": "Orders" }, timeout=30, ) data = response.json() response.raise_for_status() - Add fields. Choices for a select need an id, a name and a colour; you write the id into cells.
curl -X POST "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/fields" \ -H "Authorization: Bearer $OLAMMA_KEY" \ -H "Idempotency-Key: setup-status-field" \ -H "Content-Type: application/json" \ -d '{ "name": "Status", "type": "single_select", "options": { "choices": [ { "id": "paid", "name": "Paid", "color": "green" }, { "id": "pending", "name": "Pending", "color": "amber" } ] } }'const response = await fetch(`https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/fields`, { method: "POST", headers: { Authorization: `Bearer ${process.env.OLAMMA_KEY}`, "Idempotency-Key": "setup-status-field", "Content-Type": "application/json", }, body: JSON.stringify({ "name": "Status", "type": "single_select", "options": { "choices": [ { "id": "paid", "name": "Paid", "color": "green" }, { "id": "pending", "name": "Pending", "color": "amber" } ] } }), }); const data = await response.json(); if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os import requests response = requests.post( "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/fields", headers={"Authorization": f"Bearer {os.environ['OLAMMA_KEY']}", "Idempotency-Key": "setup-status-field"}, json={ "name": "Status", "type": "single_select", "options": { "choices": [ { "id": "paid", "name": "Paid", "color": "green" }, { "id": "pending", "name": "Pending", "color": "amber" } ] } }, timeout=30, ) data = response.json() response.raise_for_status() - Add records, up to 100 per call. Cells are keyed by field id; leave a field out to leave it empty.
curl -X POST "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records" \ -H "Authorization: Bearer $OLAMMA_KEY" \ -H "Idempotency-Key: orders-import-2026-12-01" \ -H "Content-Type: application/json" \ -d '{ "records": [ { "cells": { "tf_name": "Order 1042", "tf_status": "paid", "tf_total": 125000 } }, { "cells": { "tf_name": "Order 1043", "tf_status": "pending", "tf_total": 48000 } } ] }'const response = await fetch(`https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records`, { method: "POST", headers: { Authorization: `Bearer ${process.env.OLAMMA_KEY}`, "Idempotency-Key": "orders-import-2026-12-01", "Content-Type": "application/json", }, body: JSON.stringify({ "records": [ { "cells": { "tf_name": "Order 1042", "tf_status": "paid", "tf_total": 125000 } }, { "cells": { "tf_name": "Order 1043", "tf_status": "pending", "tf_total": 48000 } } ] }), }); const data = await response.json(); if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os import requests response = requests.post( "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records", headers={"Authorization": f"Bearer {os.environ['OLAMMA_KEY']}", "Idempotency-Key": "orders-import-2026-12-01"}, json={ "records": [ { "cells": { "tf_name": "Order 1042", "tf_status": "paid", "tf_total": 125000 } }, { "cells": { "tf_name": "Order 1043", "tf_status": "pending", "tf_total": 48000 } } ] }, timeout=30, ) data = response.json() response.raise_for_status() - Read them back through a view, filtered for this read only. Here: paid orders, largest first.
curl -G "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records" \ -H "Authorization: Bearer $OLAMMA_KEY" \ --data-urlencode 'view=tv_…' \ --data-urlencode 'sort={"fieldId":"tf_total","direction":"desc"}' \ --data-urlencode 'filters=[{"fieldId":"tf_status","op":"any_of","value":["paid"]}]'const response = await fetch(`https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records?${new URLSearchParams({"view":"tv_…","sort":"{\"fieldId\":\"tf_total\",\"direction\":\"desc\"}","filters":"[{\"fieldId\":\"tf_status\",\"op\":\"any_of\",\"value\":[\"paid\"]}]"})}`, { headers: { Authorization: `Bearer ${process.env.OLAMMA_KEY}`, }, }); const data = await response.json(); if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os import requests response = requests.get( "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records", headers={"Authorization": f"Bearer {os.environ['OLAMMA_KEY']}"}, params={"view":"tv_…","sort":"{\"fieldId\":\"tf_total\",\"direction\":\"desc\"}","filters":"[{\"fieldId\":\"tf_status\",\"op\":\"any_of\",\"value\":[\"paid\"]}]"}, timeout=30, ) data = response.json() response.raise_for_status()
Field types
Send each cell in the form its field’s type expects. null clears a cell. Empty cells are left out of answers.
| Type | Send | Example | Filter with |
|---|---|---|---|
text | Text, up to 10,000 characters | "Ada Okafor" | contains, is, empty, not_empty |
long_text | Text, up to 50,000 characters | "Left at the gate." | contains, is, empty, not_empty |
email | An email address | "ada@example.com" | contains, is, empty, not_empty |
phone | Text | "+234 803 555 0142" | contains, is, empty, not_empty |
url | An http or https address | "https://example.com" | contains, is, empty, not_empty |
number | A number, rounded to the field’s precision | 42.5 | eq, lt, gt, empty |
currency | Whole minor units, in the field’s currency | 125000 (₦1,250.00) | eq, lt, gt, empty |
percent | A number: 50 means 50% | 12.5 | eq, lt, gt, empty, not_empty |
rating | A whole number from 1 to the field’s maximum | 4 | eq, lt, gt, empty, not_empty |
duration | Whole seconds, 0 to 36,000,000 | 5400 (1 h 30 min) | eq, lt, gt, empty, not_empty |
checkbox | true or false | true | checked, unchecked |
date | A day, YYYY-MM-DD | "2026-12-15" | eq, lt, gt, empty |
single_select | One choice id | "paid" | any_of (a list of choice ids) |
multi_select | A list of choice ids | ["urgent", "vip"] | has_any (a list of choice ids) |
person | A member’s identity, from List members | "ada@yourcompany.com" | is |
link | Record ids in the linked table, or {"add": […], "remove": […]} | ["tr_…"] | empty, not_empty, has_any |
attachment | Up to 10 file ids of files uploaded in Olamma | ["tfl_…"] | is_empty, is_not_empty |
Fields that work out their own value can’t be written: formula, lookup, rollup, created_time, updated_time, created_by, updated_by, autonumber and button. Links to tasks, pages and other Olamma items can’t be written through cells yet.
Filtering and sorting a read
List records always reads through a view and uses its saved sort and filters. To narrow one read without changing the view, add any of these as JSON text:
sort | {"fieldId":"tf_…","direction":"asc"} or "desc". Long text, multi-select, links, lookups, rollups and attachments can’t be sorted. |
filters | A list of {"fieldId","op","value"}, up to 100. Operators by type are in the table above; empty-style operators take no value. |
match | "all" (every filter matches) or "any", with the quotes. |
q | Plain search text. |
Send the same values with every page. Calendar views can read a date range instead, with from and to (up to 42 days).
Changing records safely
Update records asks for each cell’s new value and the value you last read. If anyone changed a cell in between, the whole call writes nothing and answers conflict with the current values, so you never overwrite someone’s edit without knowing.
curl -X PATCH "https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records" \
-H "Authorization: Bearer $OLAMMA_KEY" \
-H "Idempotency-Key: order-1043-paid" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{
"id": "tr_…",
"cells": {
"tf_status": {
"value": "paid",
"previous": "pending"
}
}
}
]
}'const response = await fetch(`https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.OLAMMA_KEY}`,
"Idempotency-Key": "order-1043-paid",
"Content-Type": "application/json",
},
body: JSON.stringify({
"updates": [
{
"id": "tr_…",
"cells": {
"tf_status": {
"value": "paid",
"previous": "pending"
}
}
}
]
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://api.olamma.sh/v1/w/your-workspace/tables/tt_…/records",
headers={"Authorization": f"Bearer {os.environ['OLAMMA_KEY']}", "Idempotency-Key": "order-1043-paid"},
json={
"updates": [
{
"id": "tr_…",
"cells": {
"tf_status": {
"value": "paid",
"previous": "pending"
}
}
}
]
},
timeout=30,
)
data = response.json()
response.raise_for_status()- Deleting moves records to Trash; restore them, links included, within 30 days.
- History shows who changed what, and the earlier values, for 180 days.
- Comments are plain text and can mention members; nobody is emailed.
Record cells can hold text from outside your team, such as public form answers. Answers list them under untrusted; see Untrusted text.
Limits
- Up to 100 records added and 500 cells changed per call; up to 500 records deleted or restored.
- Up to 100 records per page when listing.
- A link cell holds at most 5,000 links, and one write changes at most 1,000.