Tables
Typed GTM datasets and the audit surface for row-level work.
A table is a typed dataset inside an organization. Rows hold GTM entities such as accounts, contacts, leads, signups, product events, or campaign state. Columns add behavior: enrichment, scoring, research, formulas, provider actions, and workflow links. Cells carry the output and provenance for each row.
Functions
Open Tables → Functions to browse reusable table computations. Each Function declares its inputs, the column steps to run, and the outputs it returns. Selecting a Function opens a detail panel with its published configuration, estimated credits per row and activity. View runs shows the versions and outcomes of previous calls.
Choose Edit draft configuration to work in the familiar table grid. Define the input and output fields and order the steps in the configuration panel. Use draft rows to test the columns, then save, review and publish the configuration. Running paid test columns uses the normal preview and credit approval controls.
Publishing automatically updates future calls from every connected table. Each queued or running call keeps the version it started with. Past results stay unchanged, and draft edits stay separate from the published version. Publishing never raises a connected table's credit limit; if a new required input or a removed output breaks its mapping, correct that mapping before running it again.
The same lifecycle is available from the CLI:
oxygen functions list --json
oxygen functions describe <function-id> --json
oxygen functions runs <function-id> --jsonUse functions draft --definition-json '<configuration>' to create a draft, or include a Function ID to save its complete configuration. The JSON defines the backing table, name, inputs, step columns, outputs and limits; functions draft --help describes the fields. When editing an existing draft, pass --expected-draft-hash from inspection to detect concurrent edits. Inspect the returned function.draft.contentHash, then publish exactly that reviewed draft:
oxygen functions publish <function-id> --expected-draft-hash <hash> --jsonUse functions bind --help to connect another table's columns to the Function's named inputs and outputs. Existing callables commands remain compatible. MCP exposes the same lifecycle through Manage Functions (oxygen_callables_manage).
Shape
| Field | Notes |
|---|---|
id | Stable table identifier |
name | Display name. The slug is derived from it and deduplicated workspace-wide (leads, leads-2), so duplicate names are allowed |
project | Optional project (folder) id or slug; defaults to general |
description | Free text |
columns | Ordered, typed; see Columns |
row_count | Maintained by the runtime |
web_url | Deep-link for visual inspection when returned by CLI/MCP |
Creating a table
oxygen tables create "Q1 TAM" \
--columns-json '[{"label":"Company Domain","key":"domain","data_type":"text","semantic_type":"company_domain"},{"label":"Company Name","key":"company_name","data_type":"text"}]' \
--jsonlist, describe, preview, query, export, duplicate, rename, move, archive, views, and webhook are the free rest of the group; dedupe, link, promote, and schedule set are paid and preview before they spend. Every command and flag is in the CLI reference.
Writing rows
oxygen tables insert <table-id> --rows-json '[{"domain":"acme.com"}]' --json
oxygen tables upsert <table-id> --key domain --rows-json '[{"domain":"acme.com","company_name":"Acme"}]' --jsonUse upsert for idempotent writes against a stable natural key. Use tables import --background for file-backed or generated batches that should run durably in the worker.
Deleting and restoring (the trash-can lifecycle)
delete is reversible by default: it archives the table immediately (hidden from every list and query), frees its slug for reuse, and schedules the physical purge for 14 days later (--purge-after <days> to tune, 0 = next background sweep). Until the purge runs you can undo everything with restore, which also reclaims the original slug when it's still free. Columns share the exact same lifecycle via columns delete / columns restore.
oxygen tables delete <table-id> --reason "junk from testing" --json # archive + 14-day purge timer
oxygen tables list --include-archived --json # the restorable trash, with purge dates
oxygen tables restore <table-id> --json # undo: cancels the purge, reclaims the slug
oxygen tables delete <table-id> --now --json # immediate purge: previews and refuses...
oxygen tables delete <table-id> --now --yes --json # ...until explicitly approved. Permanent.The purge (scheduled or --now --yes) is the only operation that destroys data: the physical Postgres table, its rows, columns, and run history are gone, and the only durable record is the operation event carrying a pre-destruction snapshot (visible in Observability). Scheduled purges run at or shortly after purge_scheduled_at — the background sweep visits each workspace every few minutes. archive remains the lighter verb: it only hides the table, with no purge timer and no end-state — use delete when you want cleanup with a deadline. A table backing a CRM object refuses tables delete. For a custom object, delete the object instead: oxygen crm objects delete <object> removes it and schedules the backing table on the same restorable timer (dry-run by default; --live requires --confirm, and --keep-table deregisters the object while keeping the table). The standard objects — companies, people, and deals — are permanent and are refused by that command too; tables archive is the only way to hide one.
In the web app, each active table in the left rail carries a Table actions menu: Rename, Duplicate, Move to folder, Copy link, Open in new tab, Export CSV, Delete. Archived and delete-scheduled tables stay out of that rail, so recovery is tables list --include-archived then tables restore through the CLI or MCP. Deleting from the web only ever archives — it never starts a purge timer.
Large file imports
In MCP, call oxygen_cli_file_import_prepare first so large files stay out of the assistant's context window. In a terminal:
oxygen tables import <table-id> --file leads.csv --upsert-key domain --background --json
oxygen tables import --create "From CSV" --file leads.csv --background --json--upsert-key makes a re-import update matched rows instead of duplicating them. --create <name> infers the columns from the file.
Capacity and recovery
Every workspace has the same infrastructure guard: 3,000,000 retained rows per Table, 25,000,000 retained Table rows across the workspace, a PostgreSQL warning at 20 GiB, and a hard limit on net-new PostgreSQL Workspace Table growth at 30 GiB. The byte counter includes Table heap, indexes, and TOAST; S3/object storage is separate. Run oxygen limits show --json for current, timestamped usage and see Billing & credits for the complete boundary behavior.
At a hard limit, reads, previews, queries, exports, row deletion, permanent Table purge, and non-growing updates remain available. archive and a default scheduled delete retain the physical table until purge, so use the preview-gated tables delete <table> --now --yes only when immediate, irreversible space recovery is intended.
Table-first rule
External writes mirror useful state back into the originating table. Provider record ids, sync status, errors, timestamps, run ids, and costs should be visible from cells and runs. The table is the audit log.
CSV, JSON, and XLSX files are import/export artifacts. Don't make them the durable state.