Tables

Tables live inside spaces and hold your records. Each table has a schema of typed fields (text, number, date, select, lookup, user, etc.).

List tables in a space

GET /api/space/<space_id>/tables/list

Request

curl https://infolobby.com/api/space/42/tables/list \
  -H "Authorization: Bearer il_live_..."

Response

[
  {
    "id": 101,
    "name": "Contacts",
    "space_id": 42
  },
  {
    "id": 102,
    "name": "Deals",
    "space_id": 42
  }
]

Get a table's schema

GET /api/table/<table_id>/get

Returns the table metadata and its field definitions. The settings object also carries the master-detail relationship: settings.detail_of (present only on a sub-table, holding its parent table id) and settings.owner_field (the field id linking each line back to the parent). See Master-detail records for how to read and write line items. A user field carrying options.lock is the table's record lock — see Update a table.

Example

curl https://infolobby.com/api/table/101/get \
  -H "Authorization: Bearer il_live_..."

Response

{
  "id": 101,
  "name": "Contacts",
  "space_id": 42,
  "fields": [
    {"id": "item_id", "name": "ID", "type": "number", "key": "true"},
    {"id": "name", "name": "Name", "type": "string"},
    {"id": "email", "name": "Email", "type": "string"},
    {"id": "status", "name": "Status", "type": "select", "options": [{"title":"New","color":""},{"title":"Active","color":"#86EFAC"},{"title":"Archived","color":"#D1D5DB"}]}
  ]
}

Create a table

POST /api/space/<space_id>/tables/create

Available to any account key with admin access to the workspace. Personal keys (il_user_) cannot create tables.

Request body

Field Type Required Notes
name string yes Display name
fields object[] yes Field definitions (see schema below)
db_table string no Underlying MySQL table name. Auto-generated from name if omitted.
tabs object[] no Tab grouping for fields

Field shape

Each field has at minimum name and type. Tables should always have one key field as the primary identifier.

Field types. These are the only accepted values — anything else is rejected with Unsupported field type: <type>. Note the single-line text type is string, not text.

Type Stores
key Auto-incrementing record identifier. Every table should have exactly one.
string Single-line text. See maxlen / validation below.
textarea Multi-line / rich text.
number Numeric. See decimals.
date Date, datetime, or time. See format.
select One or more choices from a fixed list. See options.
user One or more workspace users.
lookup Reference to record(s) in another table. See table.
link One or more URLs.
file File attachments. Manage contents through the Files API.
calc Formula, computed at read time. Read-only.
rollup Aggregate over a sub-table. Read-only. Requires an existing sub-table.
script PHPScript, evaluated when the record is viewed. Read-only.
button Action button. Read-only.
signature Electronic signature over a rich text document on the same record. Write-once.

id is optional on create. When omitted, the field id is derived from name (slugified, e.g. "Email Address"email_address) and de-duplicated automatically — so you can create a table by sending only name + type per field. Supply id explicitly only when you need a specific column name. Duplicate explicit ids are rejected with Duplicate field id.

Per-type configuration goes in an options object on the field. Omit it and each type falls back to its default.

Type Option Values Notes
date format date, datetime, time What the column stores. Defaults to date when omitted.
date default_value now Prefill new records with the current date/time.
string maxlen integer Max character length. Defaults to 100.
string validation regex (no delimiters) Value must match.
number decimals integer Decimal places. 0 stores as an integer.
select options array of {title, color} The choices, in display order.
select, lookup, user, link multiple boolean Allow more than one value.
lookup table {id, text} Target table. You must have access to it.
user lock read, write Record lock — scope records to the person named in this field. Single-value fields only, one per table.
signature document field id Required, and immutable once set. The textarea field (with options.format of html) being signed.
signature signer_email_field field id Where the signer's email address lives, used to send them their copy.
signature consent_text string Consent wording shown above the signature. Blank uses the standard wording.
signature intent_text string The statement the signer affirms. Blank uses the standard wording.

A date field is date-only unless you say otherwise:

{"name":"Cron Run Date","type":"date","options":{"format":"datetime"}}

Computed fields. calc, script, and button fields are virtual — they store no column and are read-only. calc resolves at query time; script and button resolve only when the record is viewed in the app, so they do not appear in record query results. All are writable-never: attempting to set one returns Unassignable - cannot set computed field: <name>.

A script field is defined with "type":"script" and an options object holding code (the PHPScript source) and render ("text" or "html"):

{"id":"greeting","name":"Greeting","type":"script","options":{"code":"return \"Hi \" . $record[\"name\"];","render":"text"}}

Rollup fields. rollup is not virtual — it is a stored column, recomputed from the sub-table rows every time the master record is saved, so it does appear in query results. It is read-only to callers. A rollup can only be added to a table that already has a sub-table; otherwise creation fails with A rollup field can only be added to a table that already has a sub-table.

Option Values Notes
subtable table id The sub-table to aggregate. Must already be a sub-table of this table.
aggregate sum, count, min, max, avg How to combine the rows.
field field id The sub-table field to aggregate. Not needed for count.
{"id":"invoice_total","name":"Invoice Total","type":"rollup","options":{"subtable":870,"aggregate":"sum","field":"total"}}

Signature fields. A signature field records a typed electronic signature over the rich text field named by options.document. See Signature Fields for what it stores and how verification works. Four API-visible rules:

  • options.document must name a textarea field on the same table whose options.format is html. It cannot be changed once set: a signature records the fingerprint of one specific document, so repointing it would leave existing signatures claiming to cover text nobody agreed to.
  • A signature field can never be required. The flag is stripped if you send it. The record has to exist, with the document written into it, before anyone can sign, so requiring one would make the record impossible to create.
  • The value is write-once. Setting it on a record that has already been signed returns Field "<name>" has already been signed and cannot be changed., and clearing it returns Field "<name>" has been signed and cannot be cleared here. Only a workspace admin can reset one, from the record.
  • Signatures are not available in every installation. Creating a new one where the feature is off returns Signature fields are not enabled on this installation.
{"id":"clientsig","name":"Client Signature","type":"signature","options":{"document":"agreement","signer_email_field":"email"}}

A calc field can also reference another table with @{<table_id>:<aggregate>:<field>}, and the current row with @{this:<field>} — useful when you want a computed total without a stored column.

Example

curl -X POST https://infolobby.com/api/space/42/tables/create \
  -H "Authorization: Bearer il_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Contacts",
    "fields": [
      {"id":"id","name":"ID","type":"key"},
      {"id":"name","name":"Name","type":"string"},
      {"id":"email","name":"Email","type":"string"}
    ]
  }'

Equivalent minimal form (ids derived from names — name/email):

{
  "name": "Contacts",
  "fields": [
    {"name":"ID","type":"key"},
    {"name":"Name","type":"string"},
    {"name":"Email","type":"string"}
  ]
}

Update a table

POST /api/table/<table_id>/update

⚠ Destructive if misused. The fields array you POST fully replaces the table's schema. Any field not in the payload is removed and its data is permanently dropped from the underlying MySQL table.

Recommended flow: 1. GET /api/table/<id>/get to fetch the current schema. 2. Mutate the returned fields array in place — add, edit, or remove specific entries. 3. POST the modified array back to update.

Never construct the fields array from scratch unless you intend to wipe the schema.

There is no partial update: both name and fields are required, so even renaming a table means reading the current schema and posting it back alongside the new name. Sending name alone is rejected with Invalid parameters (the schema is left intact).

Request body

Field Type Required Notes
name string yes Table display name
fields object[] yes Full field array — see warning above
tabs object[] no Tab grouping
settings.hidden bool no Hide the table from the workspace navigation

Record lock — a User field option

A user field in the fields array may carry options.lock, which scopes the table's records to the person named in that field:

{ "id": "owner", "name": "Owner", "type": "user", "options": { "lock": "read" } }
lock Effect
read A non-admin member only sees records where they are the user in that field. Records with the field empty are hidden.
write Members see every record but can only update or delete their own.

Omit the key (or send "") to unlock. The field must be of type user and must not have multiple set, at most one field per table may carry a lock, and the mode must be one of the two above — any other combination is rejected with a message rather than silently stored.

On a locked table the owner field is admin-only: a non-admin cannot set it, and it is filled with the creator automatically on create.

Workspace admins are exempt in both modes, and so is anything running server-side (automations, PHPScript, imports). Note that an account-level API key authenticates as the account owner, so it is not restricted by a record lock; a personal API key acts as its owner and is. See Record Lock for the full behaviour, including what the lock does not cover.

Example

# Step 1: fetch current schema
curl https://infolobby.com/api/table/101/get \
  -H "Authorization: Bearer il_live_..." > table.json

# Step 2: edit table.json (add a field, etc.) -- shown here as a fictional payload
# Step 3: post it back
curl -X POST https://infolobby.com/api/table/101/update \
  -H "Authorization: Bearer il_live_..." \
  -H "Content-Type: application/json" \
  -d @updated.json

Delete a table

POST /api/table/<table_id>/delete

Deletes the table definition and (for managed databases) drops the underlying MySQL table. Irreversible.

If lookup fields in other tables reference this table, they are resolved so those tables keep working. Control it with the optional on_incoming_refs parameter:

Value Effect
delete (default) Remove the referencing lookup fields.
text Convert them to text, keeping the current linked-record titles as labels.
curl -X POST https://infolobby.com/api/table/101/delete \
  -H "Authorization: Bearer il_live_..." \
  -H "Content-Type: application/json" \
  -d '{"on_incoming_refs": "text"}'