API
Read any Google Sheet you can access as JSON, CSV or NDJSON. One request, no SDK.
Quick start
This works immediately, with no account:
curl "https://sheet2json.app/api/v1/extract?url=https://docs.google.com/spreadsheets/d/SHEET_ID/edit"
With a key, you get a much higher rate limit and can read private sheets:
curl "https://sheet2json.app/api/v1/extract?url=https://docs.google.com/spreadsheets/d/SHEET_ID/edit" \ -H "Authorization: Bearer s2j_your_key_here"
Sign in with Google to create a key in Settings.
Authentication
Pass your key as a bearer token. Keys are created in Settings and shown exactly once, when created.
Authorization: Bearer s2j_xxxxxxxxxxxxxxxxxxxxxxxx
A key can be revoked at any time, which takes effect immediately. Requests with no key are still served, under a much smaller per-IP limit, so try the API before signing up for anything.
GET /api/v1/extract
Extracts one sheet tab into JSON rows keyed by the header row.
| Parameter | Required | Description |
|---|---|---|
| url | yes | The Google Sheets URL. A #gid= fragment selects one tab. |
| format | no | json (default), csv or ndjson. |
| select | no | Comma-separated columns to keep, in that order. See below. |
| where | no | One filter, e.g. where=role=Developer. Repeat it to AND several together. |
| sort | no | Columns to order by. A leading - sorts descending. |
| limit | no | Most rows to return, applied after filtering and sorting. |
curl "https://sheet2json.app/api/v1/extract?url=https://docs.google.com/spreadsheets/d/SHEET_ID/edit" \ -H "Authorization: Bearer s2j_your_key_here"
Response:
{
"spreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
"gid": "0",
"title": "Leads",
"sourceUrl": "https://docs.google.com/spreadsheets/d/1Bxi.../edit#gid=0",
"rowCount": 2,
"columnCount": 3,
"extractedAt": "2026-09-28T16:00:00.000Z",
"data": [
{ "name": "Ansh", "email": "ansh@example.com", "role": "Developer" },
{ "name": "Rahul", "email": "rahul@example.com", "role": "Designer" }
]
}Narrowing the rows
The point of an endpoint rather than a CSV download: filter, order and cap the rows server-side, so pulling five rows out of nine hundred does not mean transferring all nine hundred. Parameters are applied in a fixed order — where, then sort, then limit, then select.
curl "https://sheet2json.app/api/v1/extract?url=${SHEET_URL}&where=role=Developer" \
-H "Authorization: Bearer $S2J_KEY"Repeat where to AND filters together, so this one returns only developers named Ansh.
| Operator | Matches when |
|---|---|
| = | the value is exactly this |
| != | the value is anything but this |
| ~ | the value contains this, ignoring case |
| > | the value is greater (numbers compared as numbers) |
| >= | the value is greater or equal |
| < | the value is less |
| <= | the value is less or equal |
A column that is not in the sheet is an error, not an empty result, so a typo is reported instead of looking like a sheet with no matching rows. Numbers are compared as numbers, so where=amount>100 does what it looks like rather than sorting "9" after "100".
Other endpoints
- GET/api/v1/me
- Confirms a key works and reports the remaining quota. The first call to make when integrating.
- GET/api/v1/extractions
- Lists the extractions saved from the web UI. Accepts an optional limit, 1 to 200.
- GET/api/v1/extractions/{id}
- Reads one saved extraction, including its rows.
- DELETE/api/v1/extractions/{id}
- Deletes one of your saved extractions.
All four require an API key. A resource belonging to another account reads as 404, never 403.
Saved live endpoints
Save a sheet and its optional select, where, sort and limit recipe from the extraction result. The endpoint reads current sheet rows, applies that recipe, and keeps the same URL until you delete it. Reads use the API's short cache. The endpoint is private to your account and requires one of your API keys.
curl "https://sheet2json.app/api/v1/endpoints/ENDPOINT_ID?format=csv" \ -H "Authorization: Bearer s2j_your_key_here"
Server-side JavaScript works the same way. Keep the API key in an environment variable and out of public browser code.
const response = await fetch("https://sheet2json.app/api/v1/endpoints/ENDPOINT_ID", {
headers: { Authorization: "Bearer " + process.env.S2J_KEY },
});
const data = await response.json();- GET/api/v1/endpoints/{id}
- Fetches live rows using the saved recipe. Optional format: json (default), csv or ndjson.
View or delete saved recipes from the signed-in Endpoints page.
A saved endpoint can read a private sheet only with the Google permission connected to its owner account. The ID alone never grants access.
Private sheets
A key on its own cannot read a private sheet. Google only returns a sheet to an account that has permission to open it, so reading your own private data requires connecting your Google account with read-only access.
- Access tokens are stored encrypted and are never exposed to the API.
- The app requests
spreadsheets.readonlyand nothing else — no Drive access. - Nothing is ever written to your spreadsheet.
- Disconnecting, in Settings, deletes the stored tokens immediately.
Connect your Google account to enable this.
Rate limits
Limits are per hour, per credential. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so a client can slow down before it is rejected rather than after. A 429 also carries Retry-After.
Successful extractions are cached briefly, so polling is cheaper than it looks and will not be double-charged against your limit.
Errors
Errors come back as JSON with a stable code, so you can branch on them:
{
"error": {
"code": "SHEET_NOT_ACCESSIBLE",
"message": "This Google Sheet could not be accessed. ..."
}
}400— a missing or malformed parameter, including an unknown column in a query401— no key, or an unknown or revoked one403— the sheet is private and the account cannot open it404— no such extraction413— the sheet exceeds the row limit429— rate limited