BaseloopDocs

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, get waits whenever the API answers 429, then carries on.
  • Long values arrive cut to 200 characters, with isTruncated: true on the cell. This example keeps the cut value. See Rate limits.

Next

On this page