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

  1. Authentication
  2. Projects
  3. Sheets
  4. API Keys
  5. Project Sources
  6. Analytics
  7. AI Builder β€” generate a project + hosted app from a single prompt
  8. Hosted Apps β€” secure runtime for AI-generated apps
  9. Query Parameters
  10. Error Handling

Authentication

Overview

The API supports three authentication methods plus a fourth for hosted apps:

  1. Clerk Authentication β€” for dashboard access (browser-based)
  2. API Key Authentication β€” for programmatic access from your own backend or scripts
  3. Public access β€” for projects you've explicitly marked isPublic
  4. 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}/session and 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: true and never surfaced in GET /api/keys. They are revocable in the database for incident response (set is_active = false).


Projects

List Projects

Get all projects for the authenticated user.

http
GET /api/projects

Headers:

  • Authorization: Bearer <clerk_token> (for dashboard)
  • x-org-id: <org_id> (optional, for organization projects)

Response:

json
{
  "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.

http
GET /api/projects/{projectId}

Headers:

  • Authorization: Bearer <clerk_token>

Response:

json
{
  "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.

http
POST /api/projects

Headers:

  • Authorization: Bearer <clerk_token>
  • Content-Type: application/json
  • x-org-id: <org_id> (optional)

Request Body:

json
{
  "spreadsheetId": "1abc123def456...",
  "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1abc123def456...",
  "name": "My New Project",
  "description": "Optional description"
}

Response:

json
{
  "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.

http
DELETE /api/projects/{projectId}

Headers:

  • Authorization: Bearer <clerk_token>

Response:

json
{
  "success": true,
  "message": "Project deleted successfully"
}

Sheets

Get Sheet Data

Retrieve data from a sheet with advanced filtering, sorting, and aggregation.

http
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:

http
GET /api/sheets/{sheetId}?filter={"status":"active","price":{"$gt":10}}&sort=price:desc&limit=50&offset=0

Response:

json
{
  "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:

http
GET /api/sheets/{sheetId}?aggregate={"$sum":"price","$avg":"quantity","$groupBy":"category"}

Response:

json
{
  "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.

http
POST /api/sheets/{sheetId}

Headers:

  • Authorization: Bearer <api_key> (requires write scope)
  • Content-Type: application/json

Request Body (Single Row):

json
{
  "name": "New Item",
  "price": 29.99,
  "status": "active",
  "category": "Electronics"
}

Request Body (Bulk Insert - Array):

json
[
  {
    "name": "Item 1",
    "price": 19.99,
    "status": "active"
  },
  {
    "name": "Item 2",
    "price": 24.99,
    "status": "active"
  }
]

Response (Single):

json
{
  "success": true,
  "data": {
    "id": "row_1234567890",
    "name": "New Item",
    "price": 29.99,
    "status": "active",
    "category": "Electronics"
  }
}

Response (Bulk):

json
{
  "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.

http
PATCH /api/sheets/{sheetId}

Headers:

  • Authorization: Bearer <api_key> (requires write scope)
  • Content-Type: application/json

Request Body:

json
{
  "rowId": "row_1234567890",
  "data": {
    "price": 34.99,
    "status": "updated"
  }
}

Response:

json
{
  "success": true,
  "data": {
    "id": "row_1234567890",
    "name": "New Item",
    "price": 34.99,
    "status": "updated",
    "category": "Electronics"
  }
}

Delete Row

Delete a row from a sheet.

http
DELETE /api/sheets/{sheetId}

Headers:

  • Authorization: Bearer <api_key> (requires write scope)

Query Parameters:

  • rowId (required): The row ID to delete

Example:

http
DELETE /api/sheets/{sheetId}?rowId=row_1234567890

Response:

json
{
  "success": true,
  "message": "Row deleted successfully"
}

Bulk Operations

Perform multiple operations in a single request.

http
POST /api/sheets/{sheetId}/bulk

Headers:

  • Authorization: Bearer <api_key> (requires write scope)
  • Content-Type: application/json

Request Body:

json
{
  "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:

json
{
  "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.

http
GET /api/keys?projectId={projectId}

Headers:

  • Authorization: Bearer <clerk_token>

Query Parameters:

  • projectId (required): The project ID

Response:

json
{
  "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.

http
POST /api/keys

Headers:

  • Authorization: Bearer <clerk_token>
  • Content-Type: application/json

Request Body:

json
{
  "name": "Production API Key",
  "projectId": "uuid",
  "scopes": ["read", "write"],
  "expiresInDays": 365,
  "allowedIps": ["192.168.1.1", "10.0.0.1"],
  "allowedDomains": ["example.com"]
}

Response:

json
{
  "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.

http
PATCH /api/keys/{keyId}

Headers:

  • Authorization: Bearer <clerk_token>
  • Content-Type: application/json

Request Body:

json
{
  "name": "Updated Key Name",
  "scopes": ["read"],
  "allowedIps": ["192.168.1.1"],
  "allowedDomains": ["example.com"]
}

Response:

json
{
  "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.

http
DELETE /api/keys/{keyId}

Headers:

  • Authorization: Bearer <clerk_token>

Response:

json
{
  "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.

http
GET /api/keys/{keyId}/usage

Headers:

  • 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:

json
{
  "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.

http
GET /api/projects/{projectId}/sources

Headers:

  • Authorization: Bearer <clerk_token>

Response:

json
{
  "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.

http
POST /api/projects/{projectId}/sources

Headers:

  • Authorization: Bearer <clerk_token>
  • Content-Type: application/json

Request Body:

json
{
  "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 on joinKey (requires joinKey)
  • append: Append data from sources sequentially

Response:

json
{
  "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.

http
PATCH /api/projects/{projectId}/sources/{sourceId}

Headers:

  • Authorization: Bearer <clerk_token>
  • Content-Type: application/json

Request Body:

json
{
  "name": "Updated Source Name",
  "mergeStrategy": "join",
  "joinKey": "product_id",
  "isActive": false
}

Response:

json
{
  "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.

http
DELETE /api/projects/{projectId}/sources/{sourceId}

Headers:

  • Authorization: Bearer <clerk_token>

Response:

json
{
  "success": true,
  "message": "Source removed successfully"
}

Analytics

Get Analytics

Get analytics and usage statistics.

http
GET /api/analytics

Headers:

  • Authorization: Bearer <clerk_token>

Query Parameters:

  • orgId (optional): Organization ID
  • period (optional): Time period - 7d, 30d, 90d, all (default: 30d)

Response:

json
{
  "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.

http
GET /api/logs

Headers:

  • Authorization: Bearer <clerk_token>

Query Parameters:

  • orgId (optional): Organization ID
  • limit (optional): Maximum number of logs (default: 50, max: 100)

Response:

json
{
  "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.

http
POST /api/ai/build/classify

Headers:

  • Authorization: Bearer <clerk_token>
  • Content-Type: application/json

Request Body:

json
{
  "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):

json
{
  "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):

json
{
  "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.

http
POST /api/ai/build/generate

Request Body:

json
{ "jobId": "f47ac10b-..." }

Response:

json
{
  "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.

http
POST /api/ai/build/create

Request Body:

json
{ "jobId": "f47ac10b-..." }

Response (201):

json
{
  "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

http
POST /api/app/{slug}/session

Issues 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:

json
{
  "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

http
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_N

Headers:

  • 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:

js
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:

js
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 - Equality
  • field_gt=value - Greater than
  • field_lt=value - Less than
  • field_gte=value - Greater than or equal
  • field_lte=value - Less than or equal
  • field_ne=value - Not equal

JSON Filter Format

json
{
  "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:

json
{
  "$and": [{ "status": "active" }, { "price": { "$gt": 10 } }]
}
json
{
  "$or": [{ "status": "active" }, { "status": "pending" }]
}

Full-Text Search:

json
{
  "$search": "search term"
}

Searches across all columns.

Date Range:

json
{
  "date": {
    "$dateRange": {
      "start": "2024-01-01",
      "end": "2024-12-31",
      "timezone": "UTC"
    }
  }
}

Sorting

?sort=name:asc,price:desc

Format: field:direction (comma-separated)

  • asc - Ascending
  • desc - 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

json
{
  "$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

json
{
  "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:

json
{
  "error": "Unauthorized"
}

403 Forbidden:

json
{
  "error": "Insufficient permissions",
  "message": "Write access required"
}

404 Not Found:

json
{
  "error": "Sheet not found"
}

400 Bad Request:

json
{
  "error": "Invalid JSON body"
}

429 Too Many Requests:

json
{
  "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 allowed
  • X-RateLimit-Remaining: Remaining requests
  • X-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

  1. Always use HTTPS in production
  2. Store API keys securely - Never commit them to version control
  3. Use appropriate scopes - Only grant write access when needed
  4. Implement retry logic - Handle rate limits and temporary errors
  5. Cache responses - Use cache headers to reduce API calls
  6. Monitor usage - Check analytics regularly to stay within limits
  7. Use pagination - Don't fetch all data at once
  8. 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