OlammaAPI

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

BaseA set of related tables, like Sales, in one project. Project access decides who can see it.
TableRows of one kind, like Orders.
FieldA column with a type, like Total (currency). The type decides what cells accept.
ViewA saved way to look at a table: its sort, filters and columns. You read records through a view.
RecordA 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

  1. 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()
  2. 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()
  3. 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()
  4. 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()
  5. 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.

TypeSendExampleFilter with
textText, up to 10,000 characters"Ada Okafor"contains, is, empty, not_empty
long_textText, up to 50,000 characters"Left at the gate."contains, is, empty, not_empty
emailAn email address"ada@example.com"contains, is, empty, not_empty
phoneText"+234 803 555 0142"contains, is, empty, not_empty
urlAn http or https address"https://example.com"contains, is, empty, not_empty
numberA number, rounded to the field’s precision42.5eq, lt, gt, empty
currencyWhole minor units, in the field’s currency125000 (₦1,250.00)eq, lt, gt, empty
percentA number: 50 means 50%12.5eq, lt, gt, empty, not_empty
ratingA whole number from 1 to the field’s maximum4eq, lt, gt, empty, not_empty
durationWhole seconds, 0 to 36,000,0005400 (1 h 30 min)eq, lt, gt, empty, not_empty
checkboxtrue or falsetruechecked, unchecked
dateA day, YYYY-MM-DD"2026-12-15"eq, lt, gt, empty
single_selectOne choice id"paid"any_of (a list of choice ids)
multi_selectA list of choice ids["urgent", "vip"]has_any (a list of choice ids)
personA member’s identity, from List members"ada@yourcompany.com"is
linkRecord ids in the linked table, or {"add": […], "remove": […]}["tr_…"]empty, not_empty, has_any
attachmentUp 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.
filtersA 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.
qPlain 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.