Documentation

Complete guide to using xltoapi and turning your Google Sheets into REST APIs.

Quick start

1. Sign in with Google
Click "Start for free" and authorize access to your Google Sheets. We need read/write permissions to create your API.
2. Connect your spreadsheet

Go to Connections → New connection and paste your Google Sheet URL or ID. Make sure the first row contains column names (headers).

Example URL: https://docs.google.com/spreadsheets/d/abc123/edit

3. Create an API key
Go to API Keys → New key. Select permissions (read, write, delete) and copy the key. It's only shown once.
4. Make your first request
curl https://xltoapi.com/api/v1/sheets/YOUR_CONNECTION_ID \
  -H "x-api-key: xlta_your_key_here"

Authentication

All API requests require an API key in the x-api-key header.

// Required header in all requests
x-api-key: xlta_your_key_here

Keys have permissions (scopes): read, write, delete. Each endpoint requires the corresponding scope.

Base URL

https://xltoapi.com/api/v1/sheets/{connectionId}

Replace {connectionId} with your connection ID (found in the dashboard).

Tabs

By default, each connection points to the sheet (tab) configured when it was created. To access other tabs from the same spreadsheet, add /tabs/{tabName} to the URL:

# Default sheet (configured in connection)
curl "https://xltoapi.com/api/v1/sheets/ID" \
  -H "x-api-key: xlta_YOUR_KEY"

# Specific tab
curl "https://xltoapi.com/api/v1/sheets/ID/tabs/Sales" \
  -H "x-api-key: xlta_YOUR_KEY"

# Tab with spaces (URL-encoded)
curl "https://xltoapi.com/api/v1/sheets/ID/tabs/Sheet%201" \
  -H "x-api-key: xlta_YOUR_KEY"

All sub-routes work the same with /tabs/{tabName}:

RouteDescription
/tabs/{tab}CRUD (GET, POST, PUT, PATCH, DELETE)
/tabs/{tab}/infoColumn info and metadata
/tabs/{tab}/row/{rango}Row or specific range
/tabs/{tab}/search?col=valMulti-column search
/tabs/{tab}/filter/{col}/{val}Filter by URL
/tabs/{tab}/agg/{op}?column=colAggregations
/tabs/{tab}/pivot/{col}Pivot table
Note: If the tab name contains spaces or special characters, use URL-encoding (e.g.: Hoja%201 for "Hoja 1"). Without /tabs/, the tab configured in the connection is used.

GET Get data

Returns the rows of your spreadsheet as JSON. Supports filters, pagination, sorting, and multiple formats.

curl "https://xltoapi.com/api/v1/sheets/ID?limit=10&sort=price&order=desc" \
  -H "x-api-key: xlta_..."

Response:

{
  "success": true,
  "data": [
    { "_row": 2, "name": "Laptop", "price": "1200", "stock": "15" },
    { "_row": 3, "name": "Mouse", "price": "25", "stock": "200" }
  ],
  "meta": { "total": 50, "limit": 10, "offset": 0, "format": "records" }
}

The _row field indicates the row number in the sheet (row 1 is the headers, data starts at row 2).

POST Add rows

Add one or several rows to the end of your sheet. Send an object for one row or an array for multiple.

// One row
curl -X POST "https://xltoapi.com/api/v1/sheets/ID" \
  -H "x-api-key: xlta_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Keyboard", "price": "75", "stock": "50"}'

// Multiple rows
curl -X POST "https://xltoapi.com/api/v1/sheets/ID" \
  -H "x-api-key: xlta_..." \
  -H "Content-Type: application/json" \
  -d '[
    {"name": "Monitor", "price": "300", "stock": "20"},
    {"name": "Webcam", "price": "45", "stock": "80"}
  ]'

Response:

{ "success": true, "data": { "rows_added": 2 } }

PUT Replace full row

Replaces the entire row. Fields you don't send will be left empty. You must include _row.

curl -X PUT "https://xltoapi.com/api/v1/sheets/ID" \
  -H "x-api-key: xlta_..." \
  -H "Content-Type: application/json" \
  -d '{"_row": 3, "name": "Mouse Pro", "price": "45", "stock": "150"}'

Response:

{ "success": true, "data": { "updated_row": 3, "mode": "full" } }
If you only send {example}, the columns precio and stock will be left empty. To update only some fields, use PATCH.

PATCH Partial update

Updates only the fields you send. The rest remain unchanged. You must include _row.

curl -X PATCH "https://xltoapi.com/api/v1/sheets/ID" \
  -H "x-api-key: xlta_..." \
  -H "Content-Type: application/json" \
  -d '{"_row": 3, "price": "55"}'

Response:

{ "success": true, "data": { "updated_row": 3, "mode": "partial" } }

Only precio is updated. The columns nombre, stock, etc. are not modified.

DELETE Delete row

Deletes a row from the sheet. The row is completely removed (not just cleared).

curl -X DELETE "https://xltoapi.com/api/v1/sheets/ID" \
  -H "x-api-key: xlta_..." \
  -H "Content-Type: application/json" \
  -d '{"_row": 3}'

Response:

{ "success": true, "data": { "deleted_row": 3 } }
When you delete a row, the rows below shift up. The _row numbers will change. If you need to delete multiple rows, do it from last to first, or use the DELETE by filter endpoint.

Filters and search

Exact filters

Filter rows by exact value using filter[column]=value. You can combine multiple filters.

# Simple filter
GET /api/v1/sheets/ID?filter[category]=Electronics

# Multiple filters (AND)
GET /api/v1/sheets/ID?filter[category]=Electronics&filter[brand]=Samsung

Wildcards (partial search)

Use * as a wildcard for partial searches.

  • filter[name]=*laptop* Contains 'laptop'
  • filter[name]=Mac* Starts with 'Mac'
  • filter[name]=*Pro Ends with 'Pro'
# Products containing "pro" (case-insensitive)
GET /api/v1/sheets/ID?filter[name]=*pro*&search_ci=true

Numeric operators

Use query[column]=__operator(value) for numeric comparisons.

OperatorDescriptionExample
__eq(n)Equal toquery[stock]=__eq(0)
__ne(n)Not equal toquery[stock]=__ne(0)
__gt(n)Greater thanquery[price]=__gt(100)
__gte(n)Greater or equalquery[price]=__gte(100)
__lt(n)Less thanquery[stock]=__lt(10)
__lte(n)Less or equalquery[stock]=__lte(10)
# Products with price > 100 and stock < 10
GET /api/v1/sheets/ID?query[price]=__gt(100)&query[stock]=__lt(10)

# Combine with text filters
GET /api/v1/sheets/ID?filter[category]=Electronics&query[price]=__lte(500)

Case-insensitive search

Add search_ci=true so that searches are case-insensitive. Applies to exact filters, wildcards, and URL-based search.

# "john", "John", "JOHN" all match
GET /api/v1/sheets/ID?filter[name]=john&search_ci=true

Pagination and sorting

  • limit=10 Maximum rows to return (default: 100)
  • offset=20 Skip the first N rows (default: 0)
  • sort=price Sort by this column
  • order=asc|desc Sort direction (default: asc)
# Page 3 with 10 results per page, sorted by price descending
GET /api/v1/sheets/ID?limit=10&offset=20&sort=price&order=desc

Sorting is smart: if values are numeric, it sorts numerically (9 < 10). If text, it sorts alphabetically.

Special endpoints

GET Row by index or range

Get a specific row or range of rows directly by number.

# Specific row
GET /api/v1/sheets/ID/row/2

# Range of rows (2, 3, 4, 5)
GET /api/v1/sheets/ID/row/2:5

Response (specific row):

{
  "success": true,
  "data": { "_row": 2, "name": "Laptop", "price": "1200", "stock": "15" }
}

Response (range):

{
  "success": true,
  "data": [
    { "_row": 2, "name": "Laptop", "price": "1200", "stock": "15" },
    { "_row": 3, "name": "Mouse", "price": "25", "stock": "200" }
  ]
}

The minimum row is 2 (row 1 is the headers).

GET Filter by URL

Simplified form of filtering: /filter/{column}/{value}

# Rows where category = "Electronics"
GET /api/v1/sheets/ID/filter/category/Electronics

# With case-insensitive
GET /api/v1/sheets/ID/filter/name/laptop?search_ci=true

Response:

{
  "success": true,
  "data": [
    { "_row": 2, "name": "Laptop", "category": "Electronics", "price": "1200" }
  ],
  "meta": { "total": 1 }
}

DELETE Delete by filter

Delete all rows matching the filter. Useful for deleting multiple records at once without knowing row numbers.

# Delete all rows where status = "inactive"
curl -X DELETE "https://xltoapi.com/api/v1/sheets/ID/filter/status/inactive" \
  -H "x-api-key: xlta_..."

# Case-insensitive
curl -X DELETE "https://xltoapi.com/api/v1/sheets/ID/filter/status/INACTIVE?search_ci=true" \
  -H "x-api-key: xlta_..."

Response:

{ "success": true, "data": { "deleted_rows": 5 } }
This operation cannot be undone. Rows are permanently deleted from the sheet.

Analytics

GET Aggregations

Calculate mathematical operations on the numeric columns of your sheet.

OperationDescription
sumTotal sum
maxMaximum value
minMinimum value
meanAverage
medianMedian
countCount of numeric values
# Sum of column "price"
GET /api/v1/sheets/ID/agg/sum?column=price

# Average of all numeric columns
GET /api/v1/sheets/ID/agg/mean

Response:

{
  "success": true,
  "data": {
    "operation": "sum",
    "column": "price",
    "result": { "price": 15750 }
  }
}

If you don't send column, the operation applies to all columns containing numeric data. Columns without numeric data return null.

GET Info / Statistics

Get detailed statistics for each column in your sheet.

GET /api/v1/sheets/ID/info

Response:

{
  "success": true,
  "data": {
    "totalRows": 150,
    "totalColumns": 5,
    "columns": {
      "name": {
        "count": 150,
        "unique": 120,
        "nulls": 0
      },
      "price": {
        "count": 148,
        "unique": 45,
        "nulls": 2,
        "numeric": {
          "mean": 350.5,
          "std": 125.3,
          "min": 10,
          "max": 1500,
          "p25": 99,
          "p50": 250,
          "p75": 499
        }
      }
    }
  }
}

For each column: count (non-empty values), unique (unique values), nulls (empty cells). If the column has numeric data, it includes additional statistics with percentiles.

GET Pivot tables (Pivot)

Group data by a column and apply an aggregation operation. Similar to Excel pivot tables.

  • _agg=sum|max|min|mean|count Aggregation operation (default: count)
  • _value=column Column to operate on (default: all numeric)
# Count products by category
GET /api/v1/sheets/ID/pivot/category

# Sum of sales by region
GET /api/v1/sheets/ID/pivot/region?_agg=sum&_value=sales

# Average price by brand
GET /api/v1/sheets/ID/pivot/brand?_agg=mean&_value=price

Response:

{
  "success": true,
  "data": {
    "groupBy": "category",
    "aggregation": "sum",
    "valueColumn": "price",
    "result": {
      "Electronics": { "price": 8500 },
      "Clothing": { "price": 3200 },
      "Home": { "price": 4050 }
    }
  }
}

Response formats

Use _format to change the JSON format returned by the main GET.

FormatDescription
recordsArray of objects (default)
dictObject of objects: {column: {row: value}}'
listColumns as arrays: {column: [values]}
splitSeparated: {columns: [], data: [][], index: []}
# Dict format
GET /api/v1/sheets/ID?_format=dict
// { "name": { "2": "Laptop", "3": "Mouse" }, "price": { "2": "1200", "3": "25" } }

# List format
GET /api/v1/sheets/ID?_format=list
// { "name": ["Laptop", "Mouse"], "price": ["1200", "25"] }

# Split format
GET /api/v1/sheets/ID?_format=split
// { "columns": ["name", "price"], "data": [["Laptop", "1200"], ["Mouse", "25"]], "index": [2, 3] }

Raw mode

Add _raw=1 to get data as an array of arrays, without the success/data/meta wrapping. The first row is the headers.

GET /api/v1/sheets/ID?_raw=1

Response:

[
  ["name", "price", "stock"],
  ["Laptop", "1200", "15"],
  ["Mouse", "25", "200"],
  ["Keyboard", "75", "50"]
]

Can be combined with filters, pagination, and sorting: ?_raw=1&sort=precio&order=desc&limit=5

Embeddable SDK

Insert a table with your sheet's data on any web page with a single line of code. No dependencies, no frameworks — just a '<'script'>'.

<!-- Add this where you want to show the table -->
<div id="xltoapi-table"></div>
<script src="https://xltoapi.com/api/v1/embed?id=YOUR_CONNECTION_ID&key=xlta_your_key"></script>

Script parameters:

  • id (required) Your connection ID
  • key (required) Your API key
  • theme=light|dark Visual theme (default: light)
  • limit=100 Maximum rows to display
  • title=My data Optional title above the table
  • target=my-div HTML element ID to render in (default: xltoapi-table)

Example with dark theme and title:

<div id="products"></div>
<script src="https://xltoapi.com/api/v1/embed?id=abc123&key=xlta_xxx&theme=dark&title=Products&target=products&limit=20"></script>

Full example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My page</title>
</head>
<body>
  <h1>Inventory</h1>
  <p>Real-time data from Google Sheets:</p>

  <div id="xltoapi-table"></div>
  <script src="https://xltoapi.com/api/v1/embed?id=YOUR_ID&key=xlta_xxx&theme=light&title=Inventory&limit=50"></script>
</body>
</html>
Note: The script is cached for 5 minutes. Data is fetched on each page load. The table includes its own styles that don't interfere with your site's.

Response headers

All GET responses include headers with useful information.

HeaderDescription
X-RateLimit-LimitRequest limit per minute
X-RateLimit-RemainingRemaining requests this minute
X-RateLimit-ResetTimestamp (epoch) when the counter resets
X-Sheet-RowsTotal data rows in the sheet
X-Sheet-ColumnsTotal columns in the sheet

Rate limiting

Each plan has request limits per minute and per month. When you exceed the limit, you'll receive a 429 error.

PlanPer minutePer month
Free301,000
Pro12050,000
Enterprise600500,000
// Error 429
{
  "success": false,
  "error": "Rate limit exceeded",
  "mensaje": "Rate limit exceeded"
}

HTML Template

Copy this template to display data from your API on any web page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My data</title>
  <style>
    table { border-collapse: collapse; width: 100%; }
    th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
    th { background: #f5f5f5; font-weight: 600; }
    tr:hover { background: #f9f9f9; }
  </style>
</head>
<body>
  <h1>My data</h1>
  <table>
    <thead><tr id="headers"></tr></thead>
    <tbody id="body"></tbody>
  </table>

  <script>
    const API_URL = "https://xltoapi.com/api/v1/sheets/YOUR_ID";
    const API_KEY = "xlta_your_key";

    fetch(API_URL, { headers: { "x-api-key": API_KEY } })
      .then(res => res.json())
      .then(({ data }) => {
        if (!data.length) return;
        const keys = Object.keys(data[0]).filter(k => k !== "_row");
        document.getElementById("headers").innerHTML =
          keys.map(k => `<th>${k}</th>`).join("");
        document.getElementById("body").innerHTML =
          data.map(row =>
            "<tr>" + keys.map(k => `<td>${row[k] ?? ""}</td>`).join("") + "</tr>"
          ).join("");
      });
  </script>
</body>
</html>

Endpoint summary

MethodEndpointDescription
GET/sheets/{id}List rows with filters
POST/sheets/{id}Add rows
PUT/sheets/{id}Replace full row
PATCH/sheets/{id}Partial update
DELETE/sheets/{id}Delete row by _row
GET/sheets/{id}/row/{n}Row by index
GET/sheets/{id}/row/{n:m}Row range
GET/sheets/{id}/filter/{col}/{val}Filter by URL
DELETE/sheets/{id}/filter/{col}/{val}Delete by filter
GET/sheets/{id}/searchMulti-column search
GET/sheets/{id}/agg/{op}Aggregations
GET/sheets/{id}/infoColumn statistics
GET/sheets/{id}/pivot/{col}Pivot table
GET/embed?id=...&key=...Embeddable JS SDK

Have questions? Contact us at soporte@xltoapi.com