Table Documents
A table document is a JSON object a user saves on a table. It is storage the API keeps for you and never reads: whatever structure you write is the structure you get back, so the front end has somewhere in-system to keep its own data rather than hosting it elsewhere and pointing a table link at it.
Shapecaster does not interpret, query, or index what is inside a document. The only rules are on its shape — the top level must be a JSON object — and its size.
Documents are owned per contribution, the same way links are. Any authenticated user may save a document on any table, and the document belongs to whoever saved it, so one table can carry documents from several different user tags. Only that owner can delete it, not the table's owner.
Documents are immutable. There is no update endpoint: replacing one means deleting it and saving another.
Like links, documents are not nested into the table object — a table that has collected several payloads would otherwise weigh down every list, search, and trending response.
The table document object
| Attribute | Type | Description |
|---|---|---|
| id | uuid | The unique identifier of the document. |
| table_id | uuid | The unique identifier of the table the document is saved on. |
| label | string (optional) | A human-readable label for the document. Up to 100 characters. null when none was given. |
| data | object | The payload, returned exactly as it was saved. Only present when fetching a single document. |
| user_tag | uuid | The opaque user tag of the user that saved this document. Fixed when the document is created and never changes after. A user session token owns what it saves, so it never sends this. An administrator names the owner with it. |
{
"id": "5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9",
"table_id": "984c03a9-7a64-4046-a26f-57cc773c7ec5",
"label": "Scoring worksheet",
"data": {
"weights": { "flavor": 0.5, "texture": 0.3, "value": 0.2 },
"notes": "Weights agreed on the first pass"
},
"user_tag": "6f2bd2cb-3ca5-47bd-a680-64cd7cd11312"
}data is the one attribute that is not always there. Listing a table's documents returns metadata only — everything above except the payload — so the size of a collection response does not grow with the data saved on the table. Fetch a document on its own to get its payload.
Creating a document
POST https://api.cxipgroup.com/tables/:table_id/table_documents
User token required
To save a document on a table, send an authenticated request to the above endpoint with your payload in data and an optional label. Both are sent at the top level, as siblings — the stored document is the value of data, not the request body around it.
Every document has an owner. Sent with a user session token, the document is owned by that session's user tag and a user_tag in the body is ignored. Sent with an administrator token, which is bound to nobody, the owner is named with user_tag, and that tag must be one of your own user tags. The rule is the same on tables and links, and is explained once in the Authenticating guide.
You can save a document on any table, including one owned by a different user tag.
data must be a JSON object at its top level. An array, a string, or a number is rejected — a payload that starts as an object can grow new keys later without breaking anything reading it, where the other shapes cannot. Below that top level, nest as deeply as you like.
Parameters:
| Parameter | Value | Description |
|---|---|---|
| table_id | uuid (required) | The unique identifier of the table to save the document on. |
| data | object (required) | The payload. Must be a JSON object at the top level, and at most 4 MB serialized. |
| label | string (optional) | A human-readable label for the document. Up to 100 characters. |
| user_tag | uuid (admin only) | The user tag that will own the document. Required when you send an administrator token, ignored when you send a user session token. |
Request:
curl -i "https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents" \
-X POST \
-H "Authorization: Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41" \
-H "Content-Type: application/json" \
-d '{
"label": "Scoring worksheet",
"data": {
"weights": { "flavor": 0.5, "texture": 0.3, "value": 0.2 },
"notes": "Weights agreed on the first pass"
}
}'const response = await fetch("https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents", {
method: "POST",
body: JSON.stringify({
label: "Scoring worksheet",
data: {
weights: { flavor: 0.5, texture: 0.3, value: 0.2 },
notes: "Weights agreed on the first pass"
}
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": "Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41"
},
});Response:
{
"id": "5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9",
"table_id": "984c03a9-7a64-4046-a26f-57cc773c7ec5",
"label": "Scoring worksheet",
"data": {
"weights": { "flavor": 0.5, "texture": 0.3, "value": 0.2 },
"notes": "Weights agreed on the first pass"
},
"user_tag": "6f2bd2cb-3ca5-47bd-a680-64cd7cd11312"
}Status codes:
| Status | When |
|---|---|
201 Created | The document was created. Its URL is in the Location header. |
401 Unauthorized | Missing or invalid token. |
404 Not Found | No table with that table_id. |
422 Unprocessable Content | Validation failed. A missing data, a data that is not a JSON object, a payload past 4 MB, or a label past 100 characters lands here. So does an administrator token sending no user_tag, or one naming a tag that isn't yours. |
A rejected payload says which way it was wrong: a missing data reports can't be blank, and a data that arrived as an array, string, or number reports must be a JSON object.
Listing a table's documents
GET https://api.cxipgroup.com/tables/:table_id/table_documents
User token required
To list the documents on a table, send an authenticated request to the above endpoint. This endpoint is paginated — the response is a bare array, with pagination metadata in the response headers.
The payload is omitted here. Each entry carries id, table_id, label, and user_tag, and no data. Payloads are fetched one document at a time, which keeps the cost of listing a table's documents the same however much has been saved on it.
Documents come back in the order they were saved, oldest first. Reads are not owner-scoped: the response carries every document on the table, whichever user tag saved it.
Parameters:
| Parameter | Value | Description |
|---|---|---|
| table_id | uuid (required) | The unique identifier of the table whose documents you want. |
| limit | integer (optional) | The number of records to return per page. Defaults to 20, with a maximum of 100. |
| page | integer (optional) | The page of records you want returned. 0 or 1 returns the first page. A value beyond the total number of pages returns nothing. |
Request:
curl -i -G "https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents" \
-H "Authorization: Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41" \
--data-urlencode "limit=20"const response = await fetch("https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents?limit=20", {
method: "GET",
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": "Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41"
},
});Response:
[
{
"id": "5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9",
"table_id": "984c03a9-7a64-4046-a26f-57cc773c7ec5",
"label": "Scoring worksheet",
"user_tag": "6f2bd2cb-3ca5-47bd-a680-64cd7cd11312"
},
{
"id": "a017c6bd-3f49-4e82-95d1-7b2e0ac48f36",
"table_id": "984c03a9-7a64-4046-a26f-57cc773c7ec5",
"label": null,
"user_tag": "c41a7e39-8d52-4b06-9f18-2e7bd05a6c83"
}
// ...
]Status codes:
| Status | When |
|---|---|
200 OK | The body is a bare array of the table's documents, without their payloads. |
401 Unauthorized | Missing or invalid token. |
404 Not Found | No table with that table_id. |
422 Unprocessable Content | A negative limit. Other unusable pagination values resolve rather than failing. |
Getting a document
GET https://api.cxipgroup.com/tables/:table_id/table_documents/:id
User token required
To retrieve a single document with its payload, send an authenticated request to the above endpoint. This is the only place data comes back. Reads are not owner-scoped — any valid token can retrieve any document, and the document's own owner gets no privileged view of it.
The document is looked up within the table, so a document id that exists but belongs to a different table is a 404 here.
Parameters:
| Parameter | Value | Description |
|---|---|---|
| table_id | uuid (required) | The unique identifier of the table the document is saved on. |
| id | uuid (required) | The unique identifier of the document to retrieve. |
Request:
curl -i "https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents/5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9" \
-H "Authorization: Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41"const response = await fetch("https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents/5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9", {
method: "GET",
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": "Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41"
},
});Response:
{
"id": "5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9",
"table_id": "984c03a9-7a64-4046-a26f-57cc773c7ec5",
"label": "Scoring worksheet",
"data": {
"weights": { "flavor": 0.5, "texture": 0.3, "value": 0.2 },
"notes": "Weights agreed on the first pass"
},
"user_tag": "6f2bd2cb-3ca5-47bd-a680-64cd7cd11312"
}Status codes:
| Status | When |
|---|---|
200 OK | The body is the document, payload included. |
401 Unauthorized | Missing or invalid token. |
404 Not Found | No table with that table_id, or no document with that id on it. |
Deleting a document
DELETE https://api.cxipgroup.com/tables/:table_id/table_documents/:id
User token required
To delete a document, send an authenticated request to the above endpoint. A successful delete returns an empty body.
Deletion is scoped to the document's own owner, not the table's. A user session token's tag must be the tag that saved the document — the table's owner has no say over documents other users saved on their table, and gets a 403 for trying. An administrator token acts by id, and may name a user_tag alongside only if that tag is the document's owner.
Because documents are immutable, this is also how you change one: delete it and save its replacement. The replacement is a new document with a new id.
A table's documents are deleted along with the table itself, so deleting a table needs no cleanup pass over its documents first.
Parameters:
| Parameter | Value | Description |
|---|---|---|
| table_id | uuid (required) | The unique identifier of the table the document is saved on. |
| id | uuid (required) | The unique identifier of the document to delete. |
Request:
curl -i "https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents/5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9" \
-X DELETE \
-H "Authorization: Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41"const response = await fetch("https://api.cxipgroup.com/tables/984c03a9-7a64-4046-a26f-57cc773c7ec5/table_documents/5c9d0e17-4a82-4f63-b0d5-8e13ca7b26f9", {
method: "DELETE",
headers: {
"Authorization": "Bearer ustk_b3d9f0a2-1c47-4e8a-9f65-0d2b8c7e3a41"
},
});Status codes:
| Status | When |
|---|---|
204 No Content | The document was deleted. The body is empty. |
401 Unauthorized | Missing or invalid token. |
403 Forbidden | The document was saved by a different user tag. |
404 Not Found | No table with that table_id, or no document with that id on it. |