Bulk Operations
Append thousands of rows in a single request, bulk-delete by filter, and import from JSON or CSV payloads - with transaction semantics and size limits.
Bulk endpoints let you move large volumes of data in and out of a sheet without issuing hundreds of individual requests. Use them for seeding a sheet from an existing dataset, purging stale records by a shared attribute, or syncing an external system's export file. All bulk endpoints require an Authorization: Bearer sk_... header. See authentication for key setup.
Batch append
POST /api/spreadsheets/{userKey}/{sheetName}/bulk
Send up to 1,000 rows in a single request by passing a rows array in the request body. Each element follows the same key-value shape as a single-row POST - keys must match the sheet's header row, extra keys are ignored, and missing columns are written as empty cells.
const response = await fetch(
"https://sheetsapi.gkit.mreshank.com/api/spreadsheets/YOUR_USER_KEY/Contacts/bulk",
{
method: "POST",
headers: {
Authorization: "Bearer sk_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
rows: [
{ Name: "Ada Lovelace", Email: "ada@example.com", Status: "Active" },
{ Name: "Grace Hopper", Email: "grace@example.com", Status: "Active" },
{
Name: "Margaret Hamilton",
Email: "mhamilton@example.com",
Status: "Pending",
},
],
}),
},
);
const data = await response.json();The response lists every row that was written, each with its assigned _id:
{
"success": true,
"inserted": 3,
"rows": [
{
"_id": "row_01a",
"Name": "Ada Lovelace",
"Email": "ada@example.com",
"Status": "Active"
},
{
"_id": "row_02b",
"Name": "Grace Hopper",
"Email": "grace@example.com",
"Status": "Active"
},
{
"_id": "row_03c",
"Name": "Margaret Hamilton",
"Email": "mhamilton@example.com",
"Status": "Pending"
}
]
}Requests with more than 1,000 rows are rejected with a 400 BATCH_TOO_LARGE error. Split larger datasets into multiple calls of 1,000 rows or fewer.
Bulk delete by filter
DELETE /api/spreadsheets/{userKey}/{sheetName}/bulk
Delete every row that matches a column filter in one request. The filter field uses the column:value syntax also accepted by GET query parameters.
const response = await fetch(
"https://sheetsapi.gkit.mreshank.com/api/spreadsheets/YOUR_USER_KEY/Contacts/bulk",
{
method: "DELETE",
headers: {
Authorization: "Bearer sk_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
filter: "Status:Inactive",
}),
},
);
const data = await response.json();
// { "success": true, "deleted": 47 }The filter matches rows by exact string equality against the column value. There is no partial-match or regex support in the bulk filter - use advanced filtering with GET requests if you need that. A filter that matches zero rows returns { "success": true, "deleted": 0 } and is not an error.
JSON import
POST /api/spreadsheets/{userKey}/{sheetName}/import
Import an array of objects from an application/json body. This endpoint is designed for one-time data loads and ETL pipelines. Unlike batch append, it accepts up to 10,000 rows per call and streams them to the sheet in chunks to stay within Google Sheets API rate limits.
const payload = [
{ Name: "Radia Perlman", Email: "radia@example.com", Status: "Active" },
// ... up to 10,000 objects
];
const response = await fetch(
"https://sheetsapi.gkit.mreshank.com/api/spreadsheets/YOUR_USER_KEY/Contacts/import",
{
method: "POST",
headers: {
Authorization: "Bearer sk_...",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
},
);CSV import
POST /api/spreadsheets/{userKey}/{sheetName}/import
Send a text/csv body to import from a CSV file. The first row is treated as the header and must match the sheet's existing column names. Columns in the CSV that do not appear in the sheet header are ignored; sheet columns absent from the CSV are left empty for each imported row.
const csv = `Name,Email,Status
Radia Perlman,radia@example.com,Active
Barbara Liskov,liskov@example.com,Pending`;
const response = await fetch(
"https://sheetsapi.gkit.mreshank.com/api/spreadsheets/YOUR_USER_KEY/Contacts/import",
{
method: "POST",
headers: {
Authorization: "Bearer sk_...",
"Content-Type": "text/csv",
},
body: csv,
},
);
const data = await response.json();
// { "success": true, "imported": 2, "failed": 0 }Idempotency
All bulk write endpoints (POST /bulk and POST /import) accept an Idempotency-Key header. If a request times out or receives a network error, resend the identical request with the same key - the server will return the cached result of the first successful execution instead of inserting rows a second time. Keys are scoped per sheet and expire after 24 hours.
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: "Bearer sk_...",
"Content-Type": "application/json",
"Idempotency-Key": "import-batch-2024-06-29-001",
},
body: JSON.stringify({ rows: [...] }),
});Partial failure behavior
Bulk operations are not atomic. If the sheet write is interrupted mid-batch, the response details which rows succeeded and which failed:
{
"success": false,
"inserted": 712,
"failed": 288,
"errors": [
{
"index": 712,
"row": { "Name": "", "Email": "bad-data", "Status": "Active" },
"error": {
"code": "VALIDATION_ERROR",
"message": "Name must not be empty."
}
}
]
}The top-level success field is false whenever any row fails. Rows before the failure point are committed and will not be rolled back. Use the errors array to identify which rows need to be corrected and resubmitted. If you submitted an idempotency key, the partial result is cached - submit a new key for the retry batch.
For error codes and retry guidance that apply across all endpoints, see error handling.