Administrative API
The Sheetlabs Administrative API lets administrators automate the work normally done in the Sheetlabs web application: managing data tables and their records, configuring published APIs, and managing users and groups.
Authentication and permissions
Use HTTP Basic authentication with an email address and an authentication token in place of the password. See authentication and authorisation for how to obtain and use a token.
All examples use https://sheetlabs.com as the base URL and an authentication token:
curl -u "admin@example.com:t_your_token" https://sheetlabs.com/datatables/ACME
Unless stated otherwise, successful create and update calls return JSON, and successful delete calls return an empty 200 OK response. Errors use an appropriate HTTP status and include a message explaining the problem.
Data models
Data table
| Field | Type | Notes |
|---|---|---|
name |
string | Required. Must be unique within the organisation and contain only alphanumeric characters. |
fields |
array of field objects | Required when creating or changing a table. Field names must be unique. |
fields[].name |
string | Column name. |
fields[].type |
string | One of string, integer, double, boolean, datetime, datetime-dmy, or datetime-mdy. |
group.id |
integer | Required. The table's group controls Editor access and the behaviour of APIs with group access. Use List groups to find it. |
filename |
string | Specifies the name of the file to import, which must have already been uploaded. |
delimiter |
string | The delimeter used between fields for CSVs (typically , or ; or \t). |
headersfirstrow |
boolean | Required when importing a file. Specifies whether the first row of the file contains column names. |
trimvalues |
boolean | Required when importing a file. Specifies whether to trim leading and trailing whitespace from string fields. |
selected_sheet |
integer | Required when importing a file. Specifies the sheet number to import from (zero indexed). |
allowempty |
boolean | Specifies whether empty spreadsheets should be permitted (true) or treated as an error (false) |
import_mode |
string | replace or append, used when a table update imports data. |
googlerefresh |
boolean | Enables automatic one-way refreshes from a linked Google Sheet. |
auto_manage_service |
boolean | Only for spreadsheet upload calls. Creates or adjusts a matching API as described below. |
An example data-table response, abridged to its important fields:
{
"id": 15335,
"name": "products",
"fields": [
{"name": "sku", "type": "string", "sqltype": "TEXT"},
{"name": "price", "type": "double", "sqltype": "FLOAT8"},
{"name": "active", "type": "boolean", "sqltype": "BOOLEAN"}
],
"group": {"id": 41, "name": "Default"},
"source": "csv",
"status": "Ready",
"rows": 0
}
Records
Records are JSON objects whose keys are the table's field names. Sheetlabs adds the numeric __id field to every stored record. Values submitted to the administrative record endpoints should be strings; they are converted according to the table field type. An empty string is stored as null for non-string fields.
{
"__id": 9001,
"sku": "SKU-100",
"price": 19.99,
"active": true
}
API configuration
| Field | Type | Notes |
|---|---|---|
name |
string | Required and unique within the organisation. It becomes part of the consumer API URL. |
datatable |
integer | Required data-table ID. Changing it also changes the API's group. |
description |
string | Description shown in generated consumer documentation. |
access |
string | One of public (accessible publicly without auth), authenticated (any authenticated user), group (accessible to users in the same group as the underlying datatable), or private (accessible only to the specified users in the users field). |
limit |
integer | Maximum records returned by a consumer API call; use 0 for no configured maximum. |
inputs |
array | One item for each filterable data-table field. presence is disallowed, optional, or required; partial enables * matching for strings and range enables .. ranges for numeric/date fields. |
outputs |
array | One item for each table field. Set include to control whether the field is returned to consumer API callers. |
users |
array of integers | User IDs allowed to call an API with private access. |
outputrecordid |
boolean | Indicates whether the records's __id will be included in the API response. |
post |
boolean | Indicates whether the API accepts POST requests (for creating records). |
put |
boolean | Indicates whether the API accepts PUT requests (for replacing existing records). |
patch |
boolean | Indicates whether the API accepts PATCH requests (for partially amending records). |
delete |
boolean | Indicates whether the API accepts DELETE requests (for deleting records). |
An example complete API response looks like this:
{
"id": 24680,
"name": "products",
"datatable": 15335,
"access": "authenticated",
"limit": 100,
"inputs": [
{"name": "sku", "type": "string", "presence": "optional", "partial": true, "range": false}
],
"outputs": [
{"name": "sku", "type": "string", "include": true},
{"name": "price", "type": "double", "include": true},
{"name": "active", "type": "boolean", "include": false}
],
"outputrecordid": true,
"post": false,
"put": false,
"patch": false,
"delete": false,
"users": []
}
User
| Field | Type | Notes |
|---|---|---|
id |
integer | User ID. Returned by the API; use it as user_id in user endpoints. |
forename |
string | Required when creating or replacing a user. |
surname |
string | Required when creating or replacing a user. |
email |
string | Required and unique. |
password |
string | Required on creation unless the organisation has SSO enabled. Optional on update; never returned. |
role |
string | Required. One of Admin, Editor, or User. |
status |
string | Required. One of Active, Disabled, or Pending Approval. |
groups |
array of group objects | The user's groups. Send group objects containing an id; an empty array assigns the default group. List-user responses instead contain group names. |
groups[].id |
integer | Group ID to assign. Use List groups to find it. |
notify_org_errors |
boolean | Controls whether the user receives organisation error notifications. |
token |
string | Returned for an individual user and after user creation. Treat it as a password. |
google_authenticated |
boolean | Returned for an individual user; indicates whether a Google account is linked. |
Group
| Field | Type | Notes |
|---|---|---|
id |
integer | Group ID. Returned by the API; use it as group_id in group endpoints. |
name |
string | Required when creating or updating a group. Must be unique within the organisation. |
members |
array of user objects | Required when creating or replacing a group. Send member objects containing an id. |
members[].id |
integer | User ID to add to the group. Use List users to find it. |
An example group response looks like this:
{
"id": 41,
"name": "Default",
"members": [{"id": 12345}]
}
Data tables
List data tables
GET /datatables/{organisation}
Lists the data tables visible to the authenticated user, ordered by name.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/datatables/ACME
Response:
[
{
"id": 15335,
"name": "products",
"source": "csv",
"status": "Ready",
"rows": 42,
"size": 2048,
"group": "Default",
"googleRefresh": false
}
]
Get a data table
GET /datatables/{organisation}/{datatable_id}
Returns the full table definition, including its fields and import settings. Retrieve this before updating a table: a normal metadata update expects the full schema, not a partial patch.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/datatables/ACME/15335
Response:
{
"id": 15335,
"name": "products",
"fields": [
{"name": "sku", "type": "string", "sqltype": "TEXT"},
{"name": "price", "type": "double", "sqltype": "FLOAT8"},
{"name": "active", "type": "boolean", "sqltype": "BOOLEAN"}
],
"group": {"id": 41, "name": "Default"},
"source": "csv",
"status": "Ready",
"rows": 42
}
Create an empty data table
POST /datatables/{organisation}
Creates an empty table with the supplied schema. This direct endpoint does not import a spreadsheet; use Upload a spreadsheet into a new data table when the data already exists in CSV or XLSX form.
curl -X POST -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"fields": [
{"name": "sku", "type": "string"},
{"name": "price", "type": "double"},
{"name": "active", "type": "boolean"}
],
"group": {"id": 41}
}' \
https://sheetlabs.com/datatables/ACME
Response:
{
"id": 15335,
"name": "products",
"fields": [
{"name": "sku", "type": "string", "sqltype": "TEXT"},
{"name": "price", "type": "double", "sqltype": "FLOAT8"},
{"name": "active", "type": "boolean", "sqltype": "BOOLEAN"}
],
"group": {"id": 41, "name": "Default"},
"status": "Ready",
"rows": 0
}
Update an existing data table
PUT /datatables/{organisation}/{datatable_id}
Updates a table's definition, including its name, fields, group, and Google refresh settings. It is a replacement-style operation: send the complete definition returned by Get a data table, with the changes applied. Omitting fields, for example, is not a safe way to leave fields unchanged.
Adding, removing, or changing fields alters the underlying table. To load data at the same time, set import_mode to replace or append and include compatible import metadata; use the dedicated spreadsheet upload endpoint for CSV/XLSX files. When an import is requested, the new schema must be compatible with the existing table.
curl -X PUT -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"fields": [
{"name": "sku", "type": "string"},
{"name": "price", "type": "double"},
{"name": "active", "type": "boolean"},
{"name": "category", "type": "string"}
],
"group": {"id": 41},
"googlerefresh": false
}' \
https://sheetlabs.com/datatables/ACME/15335
Response:
{
"id": 15335,
"name": "products",
"fields": [
{"name": "sku", "type": "string"},
{"name": "price", "type": "double"},
{"name": "active", "type": "boolean"},
{"name": "category", "type": "string"}
],
"group": {"id": 41, "name": "Default"},
"status": "Ready"
}
Delete a data table
DELETE /datatables/{organisation}/{datatable_id}
Deletes the table and all of its records. A table cannot be deleted while any APIs use it; delete those APIs or update them to use another table first. For a Google-backed table, Sheetlabs also stops its automatic refresh notification.
curl -i -X DELETE -u "admin@example.com:t_your_token" \
https://sheetlabs.com/datatables/ACME/15335
Response:
HTTP/1.1 200 OK
Upload a spreadsheet into a new data table
POST /datatables/upload/{organisation}
Uploads a .csv or .xlsx file and creates a table from it. The request must be multipart/form-data and the file part must be named files. The optional metadata part is a JSON string. If omitted, Sheetlabs detects the table name, headers, field types, delimiter, and sheet; the table is placed in the organisation's default group. If you include metadata.fields, then you can override the field names and types.
You can optionally include auto_manage_service: true in metadata to create a matching API in the same request. The request fails if that API name already exists.
curl -X POST -u "admin@example.com:t_your_token" \
-F "files=@products.csv" \
-F 'metadata={"name":"products","auto_manage_service":true}' \
https://sheetlabs.com/datatables/upload/ACME
Response:
{
"id": 15335,
"name": "products",
"filename": "products.csv",
"fields": [
{"name": "sku", "type": "string"},
{"name": "price", "type": "double"},
{"name": "active", "type": "boolean"}
],
"group": {"id": 41, "name": "Default"},
"status": "Ready",
"rows": 42
}
Replace or append a spreadsheet in an existing data table
PUT /datatables/upload/{organisation}/{datatable_id}
Upload a .csv or .xlsx file and import it into an existing table. Set metadata.import_mode = "replace" (the default) or append.
As with the POST request, you can optionally include auto_manage_service: true in metadata to update an existing matching API if the field names and types change. This is allowed only when the table has at most one API. Sheetlabs rebuilds the table, replaces its data, and, if the API exists, replaces that API's inputs and outputs to match the new fields.
curl -X PUT -u "admin@example.com:t_your_token" \
-F "files=@products.csv" \
-F 'metadata={"import_mode":"replace"}' \
https://sheetlabs.com/datatables/upload/ACME/15335
Response:
{
"id": 15335,
"name": "products",
"filename": "products.csv",
"status": "Ready",
"rows": 42
}
Force a Google Sheet refresh
POST /datatables/google_refresh/{datatable_id}?force=true
Queues an asynchronous refresh of a data table linked to Google Sheets. A 200 OK means the refresh was queued, not that the data has already been imported. force=true permits a manual refresh even when automatic refresh is disabled.
curl -X POST -u "admin@example.com:t_your_token" \
"https://sheetlabs.com/datatables/google_refresh/15335?force=true"
Response:
{
"code": 200,
"message": "OK"
}
Records
List records in a data table
GET /records/{organisation}/{datatable_id}
Returns all records as an array of objects, including __id. Add one or more table-field query parameters to apply exact-match filters. Unknown fields return a validation error.
curl -u "admin@example.com:t_your_token" \
"https://sheetlabs.com/records/ACME/15335?active=true"
Response:
[
{"__id": 9001, "sku": "SKU-100", "price": 19.99, "active": true},
{"__id": 9002, "sku": "SKU-101", "price": 24.99, "active": true}
]
Get one record
GET /records/{organisation}/{datatable_id}/{record_id}
Returns one record, including its __id. Currently, requesting an ID that does not exist returns an empty object rather than 404.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/records/ACME/15335/9001
Response:
{"__id": 9001, "sku": "SKU-100", "price": 19.99, "active": true}
Create a record
POST /records/{organisation}/{datatable_id}
Inserts a record into a data table. Do not send __id: Sheetlabs generates it.
curl -X POST -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{"sku":"SKU-100","price":"19.99","active":"true"}' \
https://sheetlabs.com/records/ACME/15335
Response:
{"sku": "SKU-100", "price": "19.99", "active": "true"}
Partially update a record
PATCH /records/{organisation}/{datatable_id}/{record_id}
Modifies the specified record_id. It updates only the fields included in the request. At least one field must be supplied. The response echoes the submitted fields; it is not a complete record representation.
curl -X PATCH -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{"price":"24.99"}' \
https://sheetlabs.com/records/ACME/15335/9001
Response:
{"price": "24.99"}
Replace a record
PUT /records/{organisation}/{datatable_id}/{record_id}
Replaces the record specified by record_id. Supply every field that should retain a value: omitted fields are not preserved. The response includes the __id added by Sheetlabs.
curl -X PUT -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{"sku":"SKU-100","price":"24.99","active":"true"}' \
https://sheetlabs.com/records/ACME/15335/9001
Response:
{"sku": "SKU-100", "price": "24.99", "active": "true", "__id": "9001"}
Delete a record
DELETE /records/{organisation}/{datatable_id}/{record_id}
Permanently deletes one record.
curl -i -X DELETE -u "admin@example.com:t_your_token" \
https://sheetlabs.com/records/ACME/15335/9001
Response:
HTTP/1.1 200 OK
Export records as CSV
GET /records/csv/{organisation}/{datatable_id}
Downloads every record in a table as CSV.
curl -u "admin@example.com:t_your_token" \
-o products.csv \
https://sheetlabs.com/records/csv/ACME/15335
Response:
sku,price,active
SKU-100,19.99,true
SKU-101,24.99,true
APIs
List APIs
GET /services/{organisation}
Lists APIs visible to the current caller, ordered by name. Use the returned id as api_id in the remaining API-management calls.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/services/ACME
Response:
[
{"id": 24680, "name": "products", "group": "Default", "updated": "2026-07-14T12:34:56"}
]
Get a single API
GET /services/{organisation}/{api_id}
Returns the full API configuration for the specified api_id.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/services/ACME/24680
Response:
{
"id": 24680,
"name": "products",
"datatable": 15335,
"access": "authenticated",
"limit": 100,
"inputs": [{"name": "sku", "type": "string", "presence": "optional", "partial": true, "range": false}],
"outputs": [{"name": "sku", "type": "string", "include": true}],
"outputrecordid": true,
"post": false,
"put": false,
"patch": false,
"delete": false,
"users": []
}
Create an API
POST /services/{organisation}
Creates a consumer-facing API bound to an existing data table. The configured input and output array positions correspond to the data table's fields.
curl -X POST -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"description": "Current products and prices.",
"datatable": 15335,
"access": "authenticated",
"limit": 100,
"inputs": [
{"name": "sku", "presence": "optional", "partial": true, "range": false},
{"name": "price", "presence": "optional", "partial": false, "range": true},
{"name": "active", "presence": "optional", "partial": false, "range": false}
],
"outputs": [
{"name": "sku", "include": true},
{"name": "price", "include": true},
{"name": "active", "include": false}
],
"outputrecordid": true,
"post": false,
"put": false,
"patch": false,
"delete": false,
"users": []
}' \
https://sheetlabs.com/services/ACME
Response:
{
"id": 24680,
"name": "products",
"datatable": 15335,
"access": "authenticated",
"limit": 100,
"inputs": [
{"name": "sku", "type": "string", "presence": "optional", "partial": true, "range": false}
],
"outputs": [
{"name": "sku", "type": "string", "include": true},
{"name": "price", "type": "double", "include": true},
{"name": "active", "type": "boolean", "include": false}
],
"outputrecordid": true,
"post": false,
"put": false,
"patch": false,
"delete": false,
"users": []
}
Update an API
PUT /services/{organisation}/{api_id}
Replaces an API's configuration and optionally binds it to a different data table. Send the complete configuration, including inputs, outputs, booleans, and users; this is not a partial update. The API name must remain unique in the organisation.
curl -X PUT -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"description": "Current products and prices.",
"datatable": 15335,
"access": "public",
"limit": 100,
"inputs": [
{"name": "sku", "presence": "optional", "partial": true, "range": false},
{"name": "price", "presence": "optional", "partial": false, "range": true},
{"name": "active", "presence": "optional", "partial": false, "range": false}
],
"outputs": [
{"name": "sku", "include": true},
{"name": "price", "include": true},
{"name": "active", "include": false}
],
"outputrecordid": true,
"post": true,
"put": false,
"patch": false,
"delete": false,
"users": []
}' \
https://sheetlabs.com/services/ACME/24680
Response:
{
"id": 24680,
"name": "products",
"datatable": 15335,
"access": "public",
"limit": 100,
"outputrecordid": true,
"post": true,
"put": false,
"patch": false,
"delete": false
}
Delete an API
DELETE /services/{organisation}/{api_id}
Deletes an API. It does not delete the data table or its records.
curl -i -X DELETE -u "admin@example.com:t_your_token" \
https://sheetlabs.com/services/ACME/24680
Response:
HTTP/1.1 200 OK
Users
List users
GET /users/{organisation}
Lists all users in the organisation, ordered by email. The response supplies the user_id required by the other user endpoints.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/users/ACME
Response:
[
{
"id": 12345,
"forename": "Avery",
"surname": "Admin",
"email": "avery@example.com",
"role": "Admin",
"status": "Active",
"groups": ["Default"]
}
]
Get a user
GET /users/{organisation}/{user_id}
Returns one user, including group IDs and the user's current token.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/users/ACME/12345
Response:
{
"id": 12345,
"forename": "Avery",
"surname": "Admin",
"email": "avery@example.com",
"role": "Admin",
"status": "Active",
"token": "t_example_token",
"google_authenticated": true,
"groups": [{"id": 41, "name": "Default"}]
}
Create a user
POST /users/{organisation}
Creates a user. Set emaildetails to true to email account details to the newly created user. A password is required unless SSO is enabled for the organisation. With SSO, Sheetlabs emails the SSO sign-in details instead. Provide group IDs, or send an empty array to assign the default group.
curl -X POST -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"forename": "Jamie",
"surname": "Editor",
"email": "jamie@example.com",
"password": "choose-a-strong-password",
"role": "Editor",
"status": "Active",
"groups": [{"id": 41}],
"emaildetails": true
}' \
https://sheetlabs.com/users/ACME
Response:
{
"id": 12346,
"forename": "Jamie",
"surname": "Editor",
"email": "jamie@example.com",
"role": "Editor",
"status": "Active",
"token": "t_new_user_token",
"message": "User created successfully"
}
Update a user
PUT /users/{organisation}/{user_id}
Updates a user's name, email, role, status, optional password, notification setting, and groups.
curl -X PUT -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"forename": "Jamie",
"surname": "Editor",
"email": "jamie@example.com",
"role": "Editor",
"status": "Active",
"notify_org_errors": false,
"groups": [{"id": 41}]
}' \
https://sheetlabs.com/users/ACME/12346
Response:
{
"id": 12346,
"forename": "Jamie",
"surname": "Editor",
"email": "jamie@example.com",
"role": "Editor",
"status": "Active",
"google_authenticated": true,
"groups": [{"id": 41, "name": "Default"}],
"message": "User Details Updated successfully."
}
Delete a user
DELETE /users/{organisation}/{user_id}
Deletes a user.
curl -i -X DELETE -u "admin@example.com:t_your_token" \
https://sheetlabs.com/users/ACME/12346
Response:
HTTP/1.1 200 OK
Groups
List groups
GET /groups/{organisation}
Lists groups visible to the current user. Use the returned ID when assigning a table or user to a group.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/groups/ACME
Response:
[
{"id": 41, "name": "Default", "memberCount": 2, "isDefault": true}
]
Get a group
GET /groups/{organisation}/{group_id}
Returns a group and its members.
curl -u "admin@example.com:t_your_token" \
https://sheetlabs.com/groups/ACME/41
Response:
{
"id": 41,
"name": "Default",
"members": [
{"id": 12345, "email": "avery@example.com", "role": "Admin", "status": "Active"}
]
}
Create a group
POST /groups/{organisation}
Creates a non-default group and assigns its members. The name must be unique in the organisation.
curl -X POST -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "Merchandising",
"members": [{"id": 12345}, {"id": 12346}]
}' \
https://sheetlabs.com/groups/ACME
Response:
{
"id": 42,
"name": "Merchandising",
"members": [
{"id": 12345, "email": "avery@example.com"},
{"id": 12346, "email": "jamie@example.com"}
]
}
Update a group
PUT /groups/{organisation}/{group_id}
Renames a group and replaces its membership with the supplied members list. Members removed from their last group are automatically placed in the default group.
curl -X PUT -u "admin@example.com:t_your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "Merchandising",
"members": [{"id": 12345}]
}' \
https://sheetlabs.com/groups/ACME/42
Response:
{
"id": 42,
"name": "Merchandising",
"members": [{"id": 12345, "email": "avery@example.com"}]
}
Delete a group
DELETE /groups/{organisation}/{group_id}
Deletes a non-default group. First move every data table out of the group. Sheetlabs removes the group from members and assigns the default group to anyone who would otherwise have no groups. Default groups cannot be deleted.
curl -i -X DELETE -u "admin@example.com:t_your_token" \
https://sheetlabs.com/groups/ACME/42
Response:
HTTP/1.1 200 OK