Table of Contents
π API Documentation
Complete API reference for MySheetAPI - Turn your Google Sheets into a REST API.
Base URL: https://mysheetapi.com/api
Table of Contents
- Authentication
- Projects
- Sheets
- API Keys
- Project Sources
- Analytics
- AI Builder β generate a project + hosted app from a single prompt
- Hosted Apps β secure runtime for AI-generated apps
- Query Parameters
- Error Handling
Authentication
Overview
The API supports three authentication methods plus a fourth for hosted apps:
- Clerk Authentication β for dashboard access (browser-based)
- API Key Authentication β for programmatic access from your own backend or scripts
- Public access β for projects you've explicitly marked
isPublic - App-Session JWT β short-lived (5 min) bearer token, used internally by AI-generated hosted apps. You don't typically wire this yourself; it's issued by
POST /api/app/{slug}/sessionand consumed by/api/app/{slug}/proxy/*. See Hosted Apps.
API Key Authentication
Include your API key in the request header:
Authorization: Bearer msa_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Or:
x-api-key: msa_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Note: API keys are scoped to specific projects. Each key can only access sheets within its assigned project.
Server-side keys created internally by the AI builder for hosted-app proxying are flagged
isServerSide: trueand never surfaced inGET /api/keys. They are revocable in the database for incident response (setis_active = false).
Projects
List Projects
Get all projects for the authenticated user.
GET /api/projectsHeaders:
Authorization: Bearer <clerk_token>(for dashboard)x-org-id: <org_id>(optional, for organization projects)
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "My Project",
"description": "Project description",
"googleSheetId": "1abc...",
"googleSheetUrl": "https://docs.google.com/spreadsheets/...",
"isPublic": false,
"corsAllowedOrigins": ["*"],
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"sheets": [
{
"id": "uuid",
"sheetName": "Sheet1",
"rowCount": 100,
"rawHeaders": ["name", "email", "age"]
}
]
}
]
}Get Project Details
Get detailed information about a specific project.
GET /api/projects/{projectId}Headers:
Authorization: Bearer <clerk_token>
Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "My Project",
"description": "Project description",
"googleSheetId": "1abc...",
"googleSheetUrl": "https://docs.google.com/spreadsheets/...",
"isPublic": false,
"corsAllowedOrigins": ["*"],
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"lastSyncedAt": "2024-01-01T00:00:00Z",
"sheets": [...],
"sources": [
{
"id": "uuid",
"name": "Additional Source",
"googleSheetId": "2def...",
"googleSheetUrl": "https://...",
"mergeStrategy": "union",
"joinKey": null,
"isActive": true
}
]
}
}Create Project
Connect a Google Spreadsheet and create a new project.
POST /api/projectsHeaders:
Authorization: Bearer <clerk_token>Content-Type: application/jsonx-org-id: <org_id>(optional)
Request Body:
{
"spreadsheetId": "1abc123def456...",
"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1abc123def456...",
"name": "My New Project",
"description": "Optional description"
}Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "My New Project",
"description": "Optional description",
"googleSheetId": "1abc123def456...",
"googleSheetUrl": "https://docs.google.com/spreadsheets/d/1abc123def456...",
"isPublic": false,
"corsAllowedOrigins": ["*"],
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"sheets": [...]
}
}Delete Project
Delete a project and all associated data.
DELETE /api/projects/{projectId}Headers:
Authorization: Bearer <clerk_token>
Response:
{
"success": true,
"message": "Project deleted successfully"
}Sheets
Get Sheet Data
Retrieve data from a sheet with advanced filtering, sorting, and aggregation.
GET /api/sheets/{sheetId}Headers:
Authorization: Bearer <api_key>(for API key auth)Authorization: Bearer <clerk_token>(for dashboard)
Query Parameters:
Parameter | Type | Description | Example |
|---|---|---|---|
filter | JSON string | Filter criteria (see Query Parameters) | {"status":"active"} |
sort | string | Sort field and direction | name:asc or price:desc |
limit | number | Number of results (default: 100, max: 1000) | 50 |
offset | number | Pagination offset | 0 |
fields | string | Comma-separated field list | name,email,age |
aggregate | JSON string | Aggregation functions (see Aggregations) | {"$sum":"price"} |
Simple Query Syntax: Instead of JSON filters, you can use simple query parameters:
?status=activeβ{"status":"active"}?price_gt=10β{"price":{"$gt":10}}?price_lt=100β{"price":{"$lt":100}}?name_ne=testβ{"name":{"$ne":"test"}}
Example Request:
GET /api/sheets/{sheetId}?filter={"status":"active","price":{"$gt":10}}&sort=price:desc&limit=50&offset=0Response:
{
"success": true,
"data": [
{
"name": "Item 1",
"price": 25.99,
"status": "active",
"category": "Electronics"
},
{
"name": "Item 2",
"price": 15.5,
"status": "active",
"category": "Books"
}
],
"pagination": {
"total": 150,
"limit": 50,
"offset": 0,
"hasMore": true
},
"meta": {
"cached": false,
"requestId": "uuid"
}
}With Aggregations:
GET /api/sheets/{sheetId}?aggregate={"$sum":"price","$avg":"quantity","$groupBy":"category"}Response:
{
"success": true,
"aggregations": {
"groups": [
{
"category": "Electronics",
"$sum": 1250.5,
"$avg": 25.01,
"$count": 50
},
{
"category": "Books",
"$sum": 450.25,
"$avg": 15.01,
"$count": 30
}
],
"totalGroups": 2,
"total": 80
},
"meta": {
"cached": false,
"requestId": "uuid"
}
}Create Row
Add a new row to a sheet.
POST /api/sheets/{sheetId}Headers:
Authorization: Bearer <api_key>(requireswritescope)Content-Type: application/json
Request Body (Single Row):
{
"name": "New Item",
"price": 29.99,
"status": "active",
"category": "Electronics"
}Request Body (Bulk Insert - Array):
[
{
"name": "Item 1",
"price": 19.99,
"status": "active"
},
{
"name": "Item 2",
"price": 24.99,
"status": "active"
}
]Response (Single):
{
"success": true,
"data": {
"id": "row_1234567890",
"name": "New Item",
"price": 29.99,
"status": "active",
"category": "Electronics"
}
}Response (Bulk):
{
"success": true,
"data": [
{
"id": "row_1234567890_0",
"name": "Item 1",
"price": 19.99,
"status": "active"
},
{
"id": "row_1234567890_1",
"name": "Item 2",
"price": 24.99,
"status": "active"
}
],
"count": 2
}Limits:
- Bulk insert: Maximum 1000 rows per request
Update Row
Update an existing row in a sheet.
PATCH /api/sheets/{sheetId}Headers:
Authorization: Bearer <api_key>(requireswritescope)Content-Type: application/json
Request Body:
{
"rowId": "row_1234567890",
"data": {
"price": 34.99,
"status": "updated"
}
}Response:
{
"success": true,
"data": {
"id": "row_1234567890",
"name": "New Item",
"price": 34.99,
"status": "updated",
"category": "Electronics"
}
}Delete Row
Delete a row from a sheet.
DELETE /api/sheets/{sheetId}Headers:
Authorization: Bearer <api_key>(requireswritescope)
Query Parameters:
rowId(required): The row ID to delete
Example:
DELETE /api/sheets/{sheetId}?rowId=row_1234567890Response:
{
"success": true,
"message": "Row deleted successfully"
}Bulk Operations
Perform multiple operations in a single request.
POST /api/sheets/{sheetId}/bulkHeaders:
Authorization: Bearer <api_key>(requireswritescope)Content-Type: application/json
Request Body:
{
"operations": [
{
"type": "insert",
"data": {
"name": "New Item 1",
"price": 19.99
}
},
{
"type": "update",
"rowId": "row_1234567890",
"data": {
"price": 29.99
}
},
{
"type": "insert",
"data": {
"name": "New Item 2",
"price": 24.99
}
}
]
}Response:
{
"success": true,
"results": [
{
"type": "insert",
"success": true,
"data": {
"name": "New Item 1",
"price": 19.99
}
},
{
"type": "update",
"success": true,
"rowId": "row_1234567890",
"data": {
"price": 29.99
}
},
{
"type": "insert",
"success": true,
"data": {
"name": "New Item 2",
"price": 24.99
}
}
],
"summary": {
"total": 3,
"successful": 3,
"failed": 0,
"rowsInserted": 2,
"rowsUpdated": 1,
"rowsDeleted": 0
}
}Limits:
- Maximum 100 operations per batch request
API Keys
List API Keys
Get all API keys for a project.
GET /api/keys?projectId={projectId}Headers:
Authorization: Bearer <clerk_token>
Query Parameters:
projectId(required): The project ID
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"projectId": "uuid",
"keyPrefix": "msa_abc12345...",
"name": "Production API Key",
"scopes": ["read", "write"],
"isActive": true,
"status": "active",
"usageCount": 1250,
"lastUsedAt": "2024-01-15T10:30:00Z",
"expiresAt": "2025-01-01T00:00:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"allowedIps": null,
"allowedDomains": null
}
]
}Create API Key
Generate a new API key for a project.
POST /api/keysHeaders:
Authorization: Bearer <clerk_token>Content-Type: application/json
Request Body:
{
"name": "Production API Key",
"projectId": "uuid",
"scopes": ["read", "write"],
"expiresInDays": 365,
"allowedIps": ["192.168.1.1", "10.0.0.1"],
"allowedDomains": ["example.com"]
}Response:
{
"success": true,
"data": {
"id": "uuid",
"key": "msa_abc12345def67890_xyz98765vwx43210...",
"keyPrefix": "msa_abc12345...",
"name": "Production API Key",
"projectId": "uuid",
"scopes": ["read", "write"],
"expiresAt": "2025-01-01T00:00:00Z",
"createdAt": "2024-01-01T00:00:00Z"
}
}β οΈ Important: The full API key is only returned once. Save it securely!
Update API Key
Update an API key's settings.
PATCH /api/keys/{keyId}Headers:
Authorization: Bearer <clerk_token>Content-Type: application/json
Request Body:
{
"name": "Updated Key Name",
"scopes": ["read"],
"allowedIps": ["192.168.1.1"],
"allowedDomains": ["example.com"]
}Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Updated Key Name",
"scopes": ["read"],
"status": "active",
"allowedIps": ["192.168.1.1"],
"allowedDomains": ["example.com"]
}
}Revoke API Key
Soft delete (revoke) an API key.
DELETE /api/keys/{keyId}Headers:
Authorization: Bearer <clerk_token>
Response:
{
"success": true,
"message": "API key revoked successfully"
}Note: Revoked keys are soft-deleted (not permanently removed) for audit purposes.
Get API Key Usage
Get usage statistics for a specific API key.
GET /api/keys/{keyId}/usageHeaders:
Authorization: Bearer <clerk_token>
Query Parameters:
days(optional): Number of days to look back (default: 30)limit(optional): Maximum number of logs to return (default: 50, max: 100)
Response:
{
"success": true,
"data": {
"keyId": "uuid",
"period": {
"start": "2024-01-01T00:00:00Z",
"end": "2024-01-31T23:59:59Z"
},
"statistics": {
"totalRequests": 1250,
"successfulRequests": 1200,
"failedRequests": 50,
"cacheHits": 800,
"averageResponseTime": 125.5
},
"byEndpoint": [
{
"endpoint": "/api/sheets/{id}",
"count": 1000,
"averageResponseTime": 120.5
},
{
"endpoint": "/api/sheets/{id}",
"method": "POST",
"count": 250,
"averageResponseTime": 150.2
}
],
"recentLogs": [...]
}
}Project Sources
List Project Sources
Get all data sources for a project.
GET /api/projects/{projectId}/sourcesHeaders:
Authorization: Bearer <clerk_token>
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"projectId": "uuid",
"googleSheetId": "2def...",
"googleSheetUrl": "https://...",
"name": "Additional Source",
"mergeStrategy": "union",
"joinKey": null,
"isActive": true,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
]
}Add Project Source
Add a new Google Sheet as a data source to a project.
POST /api/projects/{projectId}/sourcesHeaders:
Authorization: Bearer <clerk_token>Content-Type: application/json
Request Body:
{
"googleSheetId": "2def456ghi789...",
"googleSheetUrl": "https://docs.google.com/spreadsheets/d/2def456ghi789...",
"name": "Additional Source",
"mergeStrategy": "union",
"joinKey": "id"
}Merge Strategies:
union: Combine all rows from all sources (default)join: Join data based onjoinKey(requiresjoinKey)append: Append data from sources sequentially
Response:
{
"success": true,
"data": {
"id": "uuid",
"projectId": "uuid",
"googleSheetId": "2def456ghi789...",
"googleSheetUrl": "https://...",
"name": "Additional Source",
"mergeStrategy": "union",
"joinKey": "id",
"isActive": true,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
}Update Project Source
Update a project source configuration.
PATCH /api/projects/{projectId}/sources/{sourceId}Headers:
Authorization: Bearer <clerk_token>Content-Type: application/json
Request Body:
{
"name": "Updated Source Name",
"mergeStrategy": "join",
"joinKey": "product_id",
"isActive": false
}Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Updated Source Name",
"mergeStrategy": "join",
"joinKey": "product_id",
"isActive": false
}
}Delete Project Source
Remove a data source from a project.
DELETE /api/projects/{projectId}/sources/{sourceId}Headers:
Authorization: Bearer <clerk_token>
Response:
{
"success": true,
"message": "Source removed successfully"
}Analytics
Get Analytics
Get analytics and usage statistics.
GET /api/analyticsHeaders:
Authorization: Bearer <clerk_token>
Query Parameters:
orgId(optional): Organization IDperiod(optional): Time period -7d,30d,90d,all(default:30d)
Response:
{
"success": true,
"data": {
"overview": {
"totalRequests": 10000,
"successRate": 0.98,
"cacheHitRate": 0.75,
"averageResponseTime": 125.5
},
"usageQuotas": {
"requestsUsed": 8500,
"requestsLimit": 10000,
"sheetsUsed": 5,
"sheetsLimit": 10
},
"endpointStatistics": [
{
"endpoint": "/api/sheets/{id}",
"method": "GET",
"count": 8000,
"averageResponseTime": 120.5
}
],
"topSheets": [
{
"sheetId": "uuid",
"sheetName": "Sheet1",
"requestCount": 5000
}
],
"dailyRequestCounts": [
{
"date": "2024-01-01",
"count": 350
}
]
}
}Get API Logs
Get recent API request logs.
GET /api/logsHeaders:
Authorization: Bearer <clerk_token>
Query Parameters:
orgId(optional): Organization IDlimit(optional): Maximum number of logs (default: 50, max: 100)
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"method": "GET",
"endpoint": "/api/sheets/{id}",
"statusCode": 200,
"responseTimeMs": 125,
"cacheHit": true,
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"errorMessage": null,
"createdAt": "2024-01-15T10:30:00Z"
}
]
}AI Builder
The AI Builder turns a one-line prompt into a working project: a Google Sheet with sample data, a REST API, and a hosted single-page web app at /app/{slug}. The flow is three sequential calls: classify β generate β create. Each call references a jobId that tracks progress and lets you resume.
Auth: all AI Builder endpoints require a Clerk session (browser cookie). Programmatic use from outside a browser isn't supported yet.
Pipeline
POST /api/ai/build/classify β jobId, classification, complexity, [matchedTemplate]
POST /api/ai/build/generate β jobId, blueprint, confidence, sheetsViolations, cost
POST /api/ai/build/create β project, sheets, spreadsheet, appUrl
If classify returns a high-confidence template match, you can skip /generate and call POST /api/templates/{templateId}/instantiate directly.
Classify
Classifies a prompt against the seeded template library. Free β no LLM call. Creates an aiBuildJob row that subsequent calls reference.
POST /api/ai/build/classifyHeaders:
Authorization: Bearer <clerk_token>Content-Type: application/json
Request Body:
{
"prompt": "Expenses tracker with categories and monthly budgets",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}idempotencyKey (optional UUID): if provided and a job exists for the same key, returns the existing job instead of creating a new one.
Response (no template match β proceed to generate):
{
"success": true,
"jobId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"classification": {
"bestMatch": null,
"extractedName": "Expenses Tracker",
"suggestedCategory": "custom",
"interpretedSummary": "This looks like a custom app β we'll build a schema and simple UI from scratch."
},
"complexity": {
"score": 0,
"level": "simple",
"reasons": [],
"suggestions": []
},
"matchedTemplate": null
}Response (high-confidence template match):
{
"success": true,
"jobId": "...",
"classification": {
"bestMatch": {
"templateSlug": "restaurant-menu",
"category": "menu",
"score": 0.9
},
"extractedName": "Pizza Place",
"suggestedCategory": "menu",
"interpretedSummary": "You want a restaurant-menu app called \"Pizza Place\"β¦"
},
"complexity": { "score": 0, "level": "simple", ... },
"matchedTemplate": {
"id": "...",
"name": "Restaurant Menu",
"slug": "restaurant-menu",
"description": "...",
"category": "menu",
"sheetStructure": { ... },
"uiConfig": { "type": "menu" }
}
}Errors:
HTTP | code | When |
|---|---|---|
422 | PROMPT_TOO_BROAD | Complexity score β₯0.75 β too many distinct apps in one prompt. Response includes complexity.reasons[] and complexity.suggestions[]. |
400 | β | Body validation failed |
401 | β | No Clerk session |
Generate
Calls Claude Sonnet 4.6 to design the schema and a self-contained HTML app. Records token usage and cost on the job. Costs real money unless AI_BUILDER_FAKE_MODE=true server-side.
POST /api/ai/build/generateRequest Body:
{ "jobId": "f47ac10b-..." }Response:
{
"success": true,
"jobId": "f47ac10b-...",
"blueprint": {
"data": {
"name": "Expenses Tracker",
"description": "Track personal expenses by category and date.",
"sheets": [
{
"name": "Expenses",
"purpose": "Individual expense entries.",
"headers": [
"id",
"date",
"merchant",
"amount",
"currency",
"category_id"
],
"columnTypes": [
"string",
"date",
"string",
"number",
"string",
"reference"
],
"sampleRows": [
{
"id": "e-001",
"date": "2026-04-01",
"merchant": "Blue Bottle Coffee",
"amount": 4.5,
"currency": "USD",
"category_id": "c-food"
}
]
},
{ "name": "Categories", "...": "..." }
],
"relationships": [
{
"fromSheet": "Expenses",
"fromColumn": "category_id",
"toSheet": "Categories",
"toColumn": "id",
"relationshipType": "one-to-many"
}
]
},
"ui": {
"appHtml": "<!DOCTYPE html>...",
"appDescription": "Tailwind dashboard with add/list/delete expensesβ¦"
}
},
"confidence": {
"score": 0.92,
"reasons": [
{
"check": "has-sheets",
"passed": true,
"weight": 1,
"note": "at least one sheet tab"
},
{
"check": "rel-Expenses.category_id-to-Categories.id",
"passed": true,
"weight": 1,
"note": "relationship resolves"
}
]
},
"sheetsViolations": [],
"cost": { "usd": 0.0241 }
}Errors:
HTTP | code | When |
|---|---|---|
409 | JOB_BUSY | Job is not in classified state (already generating, generated, or failed). Includes currentStatus. |
402 | AI_COST_CAP | Per-user daily AI spend cap reached |
503 | AI_KILL_SWITCH | Global AI ceiling exceeded β ops have temporarily paused all builds |
501 | AI_UNAVAILABLE | Server has no ANTHROPIC_API_KEY configured |
422 | OUTPUT_LEAK | Output sanitiser detected leaked secrets or system-prompt material |
422 | UNSAFE_HTML | HTML safety scan blocked the generated app (external scripts, hardcoded secrets, unsafe innerHTML, etc.). Includes violations[]. |
500 | GENERATE_FAILED | Anthropic call failed after retries |
Create
Atomically provisions everything β Google Sheet, project row, sheets, relationships, server-side API key, version snapshot, rendered HTML. If any step fails, all completed steps are reversed (saga pattern), so you'll never end up with a half-built project.
POST /api/ai/build/createRequest Body:
{ "jobId": "f47ac10b-..." }Response (201):
{
"success": true,
"project": {
"id": "...",
"name": "Expenses Tracker",
"slug": "expenses-tracker",
"googleSheetId": "1abc...",
"googleSheetUrl": "https://docs.google.com/spreadsheets/d/1abc.../edit",
"builtBy": "ai",
"currentVersion": 1,
"visibility": "public",
"createdAt": "2026-04-25T10:30:00Z"
},
"sheets": [
{ "id": "...", "sheetName": "Expenses", "sheetIndex": 0, "rawHeaders": ["id", "date", "merchant", ...] },
{ "id": "...", "sheetName": "Categories", "sheetIndex": 1, "rawHeaders": [...] }
],
"spreadsheet": {
"id": "1abc...",
"url": "https://docs.google.com/spreadsheets/d/1abc.../edit"
},
"appUrl": "/app/expenses-tracker"
}Errors:
HTTP | code | When |
|---|---|---|
409 | JOB_BUSY | Job is not in generated state |
409 | NO_BLUEPRINT | Job has no usable blueprint stored |
401 | GOOGLE_TOKEN_EXPIRED | User must reconnect Google |
507 | GOOGLE_STORAGE_QUOTA_EXCEEDED | User's Drive is full |
500 | CREATE_FAILED | Saga step failed; all completed steps reversed. Response includes step, pgCode, pgDetail, pgConstraint, pgTable, pgColumn for diagnosis |
Hosted Apps
When the AI Builder finishes, the generated app is served at https://mysheetapi.com/app/{slug} as a sandboxed iframe. The app talks to your Sheet through a JWT-proxied API β no API key ever reaches the browser. You don't usually call these endpoints by hand; the embedded JavaScript does. They're documented here for security review and integration.
Issue an App-Session JWT
POST /api/app/{slug}/sessionIssues a 5-minute JWT scoped to the named project. Returns 404 when archived/deleted, 401 when the project is private.
Headers: none required. CORS allows the same origin and null (sandboxed iframes).
Response:
{
"token": "eyJhbGciOiJIUzI1NiI...",
"expiresIn": 300
}Token claims:
iss: "mysheetapi"aud: "app-iframe"scope: "app"projectId: "<uuid>"slug: "<project-slug>"exp: <iat + 300>
Rotating the server's APP_SESSION_JWT_SECRET invalidates every active session immediately.
Proxy a Sheet API Call
GET /api/app/{slug}/proxy/sheets/{sheetId}[?β¦]
POST /api/app/{slug}/proxy/sheets/{sheetId}
PATCH /api/app/{slug}/proxy/sheets/{sheetId}?rowId=row_N
DELETE /api/app/{slug}/proxy/sheets/{sheetId}?rowId=row_NHeaders:
Authorization: Bearer <session-token>β the token from/session
The proxy verifies the token, ensures the slug matches, and forwards the request to /api/sheets/{sheetId} with internal-auth that grants read+write to the project named in the claims. Path allowlist: only sheets/* is proxiable β /projects, /keys, /ai, /app are all blocked.
Response shapes are identical to the public /api/sheets/{id} endpoints.
Errors:
HTTP | When |
|---|---|
401 | Missing or invalid token |
403 | Token slug doesn't match URL slug |
403 | Path not in proxy allowlist |
4xx/5xx | Forwarded from the underlying /api/sheets/{id} call |
Runtime contract for hosted-app HTML
The AI Builder injects three globals into the generated HTML before your script runs:
window.__APP_SESSION_ENDPOINT; // POST β { token, expiresIn }
window.__APP_PROXY_BASE; // e.g. "/api/app/expenses-tracker/proxy"
window.__SHEET_IDS; // { "Expenses": "uuid", "Categories": "uuid" }A typical client looks like:
let cached = null;
async function getToken() {
if (cached && cached.exp > Date.now() / 1000 + 10) return cached.value;
const r = await fetch(window.__APP_SESSION_ENDPOINT, {
method: "POST",
credentials: "include",
});
const j = await r.json();
cached = { value: j.token, exp: Date.now() / 1000 + j.expiresIn };
return cached.value;
}
async function api(path, init = {}) {
const t = await getToken();
return fetch(window.__APP_PROXY_BASE + path, {
...init,
headers: {
...(init.headers || {}),
Authorization: "Bearer " + t,
"Content-Type": "application/json",
},
});
}
// Usage
const r = await api("/sheets/" + window.__SHEET_IDS["Expenses"]);
const { data } = await r.json();Query Parameters
Filtering
Simple Query Syntax
?status=active&price_gt=10&price_lt=100
Operators:
field=value- Equalityfield_gt=value- Greater thanfield_lt=value- Less thanfield_gte=value- Greater than or equalfield_lte=value- Less than or equalfield_ne=value- Not equal
JSON Filter Format
{
"status": "active",
"price": {
"$gt": 10,
"$lt": 100
}
}Comparison Operators:
$gt- Greater than$gte- Greater than or equal$lt- Less than$lte- Less than or equal$ne- Not equal$in- In array:{"status": {"$in": ["active", "pending"]}}$nin- Not in array$contains- String contains:{"name": {"$contains": "test"}}$startsWith- String starts with$endsWith- String ends with$regex- Regular expression:{"name": {"$regex": "^test", "$options": "i"}}
Boolean Logic:
$and- All conditions must match$or- Any condition must match$not- Negation
Examples:
{
"$and": [{ "status": "active" }, { "price": { "$gt": 10 } }]
}{
"$or": [{ "status": "active" }, { "status": "pending" }]
}Full-Text Search:
{
"$search": "search term"
}Searches across all columns.
Date Range:
{
"date": {
"$dateRange": {
"start": "2024-01-01",
"end": "2024-12-31",
"timezone": "UTC"
}
}
}Sorting
?sort=name:asc,price:desc
Format: field:direction (comma-separated)
asc- Ascendingdesc- Descending
Pagination
?limit=50&offset=0
limit: Number of results (default: 100, max: 1000)offset: Pagination offset (default: 0)
Field Projection
?fields=name,email,age
Include only specified fields. Use -field to exclude:
?fields=-password,-secret
Aggregations
{
"$sum": "price",
"$avg": "quantity",
"$count": true,
"$min": "price",
"$max": "price",
"$groupBy": "category"
}Aggregation Functions:
$sum- Sum of numeric values$avg- Average of numeric values$count- Count of rows$min- Minimum value$max- Maximum value$groupBy- Group results by field
Example:
?aggregate={"$sum":"price","$avg":"quantity","$groupBy":"category"}
Error Handling
Error Response Format
{
"error": "Error message",
"message": "Detailed error description"
}HTTP Status Codes
Code | Description |
|---|---|
200 | Success |
201 | Created |
400 | Bad Request - Invalid input |
401 | Unauthorized - Missing or invalid authentication |
403 | Forbidden - Insufficient permissions |
404 | Not Found - Resource not found |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal Server Error |
Common Errors
401 Unauthorized:
{
"error": "Unauthorized"
}403 Forbidden:
{
"error": "Insufficient permissions",
"message": "Write access required"
}404 Not Found:
{
"error": "Sheet not found"
}400 Bad Request:
{
"error": "Invalid JSON body"
}429 Too Many Requests:
{
"error": "Rate limit exceeded",
"message": "You have exceeded your rate limit. Please try again later."
}Rate Limiting
Rate limits are enforced based on your subscription tier:
- Free: 1,000 requests/month
- Starter: 50,000 requests/month
- Pro: 500,000 requests/month
- Enterprise: Unlimited
Rate limit headers are included in responses:
X-RateLimit-Limit: Maximum requests allowedX-RateLimit-Remaining: Remaining requestsX-RateLimit-Reset: Time when limit resets
CORS
CORS is configurable per project. By default, all origins are allowed (*). You can restrict access by configuring corsAllowedOrigins in your project settings.
Best Practices
- Always use HTTPS in production
- Store API keys securely - Never commit them to version control
- Use appropriate scopes - Only grant
writeaccess when needed - Implement retry logic - Handle rate limits and temporary errors
- Cache responses - Use cache headers to reduce API calls
- Monitor usage - Check analytics regularly to stay within limits
- Use pagination - Don't fetch all data at once
- Validate input - Always validate data before sending requests
Support
For questions, issues, or feature requests:
- Documentation: [Your docs URL]
- Support: [Your support email]
- Status: [Your status page URL]
Last Updated: 2024-01-15