Quickstart
Get your API key and read your first rows.
1. Get your API key
In the app, open Settings > API key and copy the key. Only organization admins can see it; if you are not an admin, ask one.
Keep the key on your server, for example in an environment variable:
export BASELOOP_API_KEY="<api-key>"2. Find your table
curl https://api-v2.baseloop.io/v1/tables \
-H "Authorization: Bearer $BASELOOP_API_KEY"{
"tables": [
{
"id": "tableId",
"name": "Prospects",
"emoji": ":chart_with_upwards_trend:",
"workspaceId": "workspaceId",
"workspaceName": "Sales",
"rowCount": 1000,
"fieldCount": 12
}
],
"total": 1
}3. Get its columns
Rows key their cells by column ID. The schema tells you which ID is which column.
curl https://api-v2.baseloop.io/v1/tables/<tableId>/schema \
-H "Authorization: Bearer $BASELOOP_API_KEY"4. Read rows
curl "https://api-v2.baseloop.io/v1/tables/<tableId>/rows?limit=10" \
-H "Authorization: Bearer $BASELOOP_API_KEY"{
"rows": [
{
"id": "rowId",
"tableId": "tableId",
"createdAt": "2026-05-24T12:00:00.000Z",
"updatedAt": "2026-05-24T12:30:00.000Z",
"createdByUserId": "userId",
"cells": {
"fieldId": { "value": "Acme", "status": "success" }
}
}
],
"limit": 10,
"nextCursor": "eyJ0eXBlIjoiY3JlYXRlZEF0RGVzYyIsLi4ufQ",
"hasNextPage": true
}5. Read the whole table
This reads every row, 100 at a time, and names each cell by its column:
const BASE_URL = 'https://api-v2.baseloop.io/v1'
const headers = { Authorization: `Bearer ${process.env.BASELOOP_API_KEY}` }
async function get(path: string) {
for (;;) {
const response = await fetch(`${BASE_URL}${path}`, { headers })
// Over the rate limit: wait as long as the API says, then try again
if (response.status === 429) {
const seconds = Number(response.headers.get('retry-after') ?? 10)
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
continue
}
const body = await response.json()
if (!response.ok) throw new Error(`${response.status}: ${body.message}`)
return body
}
}
export async function readTable(tableId: string) {
const { fields } = await get(`/tables/${tableId}/schema`)
// Two columns can share a label: the second one gets its ID added, so neither is lost
const columnNames = new Map<string, string>()
for (const field of fields) {
const taken = [...columnNames.values()].includes(field.label)
columnNames.set(field.id, taken ? `${field.label} (${field.id})` : field.label)
}
const rows = []
let cursor: string | null = null
do {
const query = new URLSearchParams({ limit: '100', ...(cursor ? { cursor } : {}) })
const page = await get(`/tables/${tableId}/rows?${query}`)
for (const row of page.rows) {
rows.push(
Object.fromEntries(
Object.entries(row.cells as Record<string, { value: unknown }>).map(([fieldId, cell]) => [
columnNames.get(fieldId) ?? fieldId,
cell.value,
])
)
)
}
cursor = page.nextCursor
} while (cursor)
return rows
}- Row reads are limited to 60 requests a minute, so this reads up to 6,000 rows a minute. On a bigger table,
getwaits whenever the API answers429, then carries on. - Long values arrive cut to 200 characters, with
isTruncated: trueon the cell. This example keeps the cut value. See Rate limits.
Next
- Narrow the rows with search and filters.
- Try any endpoint from your browser in the API reference.
- Send rows into a table with a webhook.