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.documentmust name atextareafield on the same table whoseoptions.formatishtml. 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 returnsField "<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
fieldsarray 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>/getto fetch the current schema. 2. Mutate the returnedfieldsarray in place — add, edit, or remove specific entries. 3. POST the modified array back toupdate.Never construct the
fieldsarray from scratch unless you intend to wipe the schema.There is no partial update: both
nameandfieldsare required, so even renaming a table means reading the current schema and posting it back alongside the new name. Sendingnamealone is rejected withInvalid 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"}'