Append Rows
curl --request POST \
--url https://api.example.com/v0/artifacts/{publicId}/rowsimport requests
url = "https://api.example.com/v0/artifacts/{publicId}/rows"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/v0/artifacts/{publicId}/rows', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v0/artifacts/{publicId}/rows",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v0/artifacts/{publicId}/rows"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v0/artifacts/{publicId}/rows")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v0/artifacts/{publicId}/rows")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyTable Rows
Append Rows
POST /v0/artifacts//rows — Append rows to a table
POST
/
v0
/
artifacts
/
{publicId}
/
rows
Append Rows
curl --request POST \
--url https://api.example.com/v0/artifacts/{publicId}/rowsimport requests
url = "https://api.example.com/v0/artifacts/{publicId}/rows"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/v0/artifacts/{publicId}/rows', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v0/artifacts/{publicId}/rows",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v0/artifacts/{publicId}/rows"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v0/artifacts/{publicId}/rows")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v0/artifacts/{publicId}/rows")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyAppend one or more rows to a table artifact, or upsert them on a unique column.
By default a table is lenient: new columns in the row data that do not exist in the schema are auto-added as
Each returned row carries
text type, and values are not checked against their declared type. Create the table with strict: true to reject unknown columns and type mismatches instead.
Auth: Authorization: Bearer tr_...
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
publicId | string | Yes | The artifact’s public UUID |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
rows | array | Yes | Array of row objects. Each object is a key-value map matching the table schema |
upsertOn | string | No | Column name. When set, a row whose value matches an existing row updates that row instead of inserting. The column must be declared unique: true in the schema |
Unique columns and idempotent publishing
Declare a columnunique: true in the table schema and Tokenrip rejects a duplicate with 409 DUPLICATE_UNIQUE_VALUE.
Pass upsertOn to make publishing idempotent in a single atomic call — no read-then-write, and no race in which two concurrent publishes both insert:
curl -X POST https://api.tokenrip.com/v0/artifacts/a1b2c3d4/rows \
-H "Authorization: Bearer tr_live_AbCdEfGhIjKlMnOpQrStUvWx" \
-H "Content-Type: application/json" \
-d '{
"rows": [ { "slug": "sourcing-in-equipment-finance", "title": "Updated title" } ],
"upsertOn": "slug"
}'
action, so you can tell an insert from an update.
upsertOn must name a column declared unique: true. Otherwise several rows could match and “the matching row” would be arbitrary — the request is rejected with UPSERT_COLUMN_NOT_UNIQUE.curl -X POST https://api.tokenrip.com/v0/artifacts/a1b2c3d4-e5f6-7890-abcd-ef1234567890/rows \
-H "Authorization: Bearer tr_live_AbCdEfGhIjKlMnOpQrStUvWx" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{ "company": "Acme Corp", "revenue": 50000, "priority": "high" },
{ "company": "Globex Inc", "revenue": 75000, "priority": "medium" }
]
}'
Example response
{
"ok": true,
"data": [
{
"id": "row-uuid-1",
"createdAt": "2026-04-14T08:00:00.000Z",
"action": "created"
},
{
"id": "row-uuid-2",
"createdAt": "2026-04-14T08:00:01.000Z",
"action": "created"
}
]
}
Response fields
| Field | Type | Description |
|---|---|---|
data | array | Array of created row summaries |
data[].id | string | UUID of the newly created row |
data[].createdAt | string (ISO 8601) | When the row was created |
data[].action | string | created for an insert, updated when upsertOn matched an existing row |
Errors
| Status | Code | Cause |
|---|---|---|
409 | DUPLICATE_UNIQUE_VALUE | A row would duplicate a value in a unique: true column. The body carries the offending column and value |
400 | UPSERT_COLUMN_NOT_UNIQUE | upsertOn names a column that is not declared unique: true |
400 | UNKNOWN_COLUMN | Strict table only — a row names a column not in the schema |
400 | INVALID_COLUMN_VALUE | Strict table only — a value does not match its column’s declared type |
400 | TOO_MANY_ROWS | More than 1000 rows in one call |
400 | WORKFLOW_TABLE_READONLY | The table is tool-layer managed; write through the mount-table route instead |