SheetCapture API
Send any fields to one endpoint and they're added as a new row in your Google Sheet, along with the date and time they were captured.
We never store your submissions. They go straight to your sheet.
Base URL
https://www.sheetcapture.comQuick start
- 1
Create a Google Sheet
Any sheet works. Rows go into the first tab, and if it has no header row yet we create one from the first row you send.
- 2
Share it with our API email
Click Share in Google Sheets and add this address as an Editor. Your sheet stays private to everyone else.
sheetcapture@appspot.gserviceaccount.com - 3
Copy your Sheet ID
It's the long ID in your sheet's URL:
docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit
- 4
Send a request with your access key
Your access key is on your account page once you sign up. The free trial includes 20 requests a month.
Authentication
Send your access key in an Authorization header with every request. You can always find it on your account page.
Authorization: Bearer YOUR_ACCESS_KEY
HTML forms can't set headers, so for forms send it as a hidden AccessKey field instead (see the HTML form example).
Add a row
/add-rowAppends a row to the first tab of your sheet. Each key in data is a column header and its value goes in that column.
- Keys are matched to your existing headers ignoring case, so
emailfills anEmailcolumn. - A key with no matching header gets a new column added at the end. Columns you don't send are left empty.
- A
datecolumn is always filled with the time we received the row, formatted asd/m/yyyy h:mm(for example14/3/2026 9:05). - Values are stored exactly as sent. Objects and arrays are saved as JSON text.
Headers
| Name | Type | Value | |
|---|---|---|---|
| Authorization | String | Bearer YOUR_ACCESS_KEY | Required |
| Content-Type | String | application/json | Required |
| Accept | String | application/json | Optional |
Request body
| Name | Type | Description | |
|---|---|---|---|
| GoogleSheetID | String | ID of your Google Sheet | Required |
| data | Object | The row to add: each key is a column header, each value is that cell | Required |
Sending form data? HTML forms can't nest fields or set headers, so add hidden GoogleSheetID and AccessKey fields. Every other field becomes a column.
Add a hidden _redirect field with a full URL to send visitors to your own thank-you page after they submit, instead of showing the JSON response. If something goes wrong, they're still redirected, with ?sheetcapture_error= and the error code added to the URL, so your page can show a message.
Limits
- Up to 50 columns per row.
- Column names up to 100 characters, not starting with
=,+,-or@. - Values up to 50,000 characters (Google Sheets' cell limit).
Examples
Replace YOUR_ACCESS_KEY and YOUR_SHEET_ID with your own.
const response = await fetch("https://www.sheetcapture.com/add-row", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
GoogleSheetID: "YOUR_SHEET_ID",
data: {
Name: "Ada Lovelace",
Email: "ada@example.com",
Message: "Hello!",
},
}),
});
if (!response.ok) {
const { error } = await response.json();
console.error(error);
}The HTML form posts directly to the API, then sends visitors to the _redirect page. Leave it out and they'll see the JSON response instead.
Responses
Successful requests return true. Errors return a JSON object with a readable error message and a stable code you can check in your code:
{ "error": "Google Sheet not found. Check GoogleSheetID…", "code": "sheet_not_found" }| Status / code | When |
|---|---|
| 200 — | The row was added to your sheet. One request is used. |
| 400 invalid_body | The body isn't valid JSON or form data. |
| 400 missing_sheet_id | GoogleSheetID wasn't sent. |
| 400 invalid_data | data is empty, or a column breaks one of the limits. |
| 400 invalid_redirect | _redirect isn't a full http or https URL. |
| 401 missing_key | No access key was sent. |
| 401 invalid_key | The access key doesn't match an account. |
| 402 no_credits | You've used this month's requests. |
| 403 sheet_not_shared | The sheet isn't shared with sheetcapture@appspot.gserviceaccount.com as an Editor. |
| 404 sheet_not_found | No Google Sheet has that ID. |
| 422 sheet_title_invalid | The first tab's name contains ":", which Google can't append to. |
| 502 google_error | Google Sheets returned another error. The message includes Google's reason. |
| 503 google_rate_limited | Google is rate limiting us. Wait for Retry-After seconds and try again. |
Any request that fails after authentication, including Google errors, is refunded. You only pay for rows that were written.
Usage headers
Every authenticated response tells you where you are with your allowance:
| Header | Value |
|---|---|
| X-Requests-Limit | Your plan's monthly allowance |
| X-Requests-Remaining | Requests left this month, after this one |
| X-Requests-Reset | When your allowance refills (ISO 8601) |
Requests & credits
- Each successful request uses 1 request from your plan.
- Requests that fail validation (
400) or authentication (401) are never charged. - If Google Sheets rejects the write (
403,404,422,502or503), the request is refunded automatically. - Your allowance refills to the full amount every month, on the day of the month you signed up.
- Once your allowance runs out, requests return
402until it refills, or until you upgrade.
CORS
The API accepts requests from any origin and answers OPTIONS preflight requests, so you can call it straight from the browser or from your own server.
Troubleshooting
- 403 sheet_not_shared
- Share the sheet with
sheetcapture@appspot.gserviceaccount.comas an Editor. Viewer or Commenter access isn't enough. - 404 sheet_not_found
- Copy the ID again from your sheet's URL: it's the part between
/d/and/edit. - 401 Invalid AccessKey
- Copy the access key again from your account page. It's case sensitive.
- 402 No requests remaining
- You've used this month's allowance. It refills on your monthly reset date (shown on your account page), or upgrade your plan for more.
- A new column appeared instead of filling an existing one
- Keys are matched to headers ignoring case, but otherwise must match exactly, including spaces.
E-mailandEmailare different columns. - My date field is missing
dateis reserved: it's always set to the time we received the row. Use a different name, likeevent_date.- My value shows as text instead of a formula
- Values are always stored exactly as sent, so submitted text can never run as a formula in your sheet.
- Only the first sheet is written to
- Rows are always added to the first tab in the spreadsheet. Drag the tab you want to the front.