{"openapi":"3.1.0","tags":[{"name":"Sheets","description":"Read and write sheet data via API key"},{"name":"Mini Pages","description":"Public mini app page access"}],"x-tagGroups":[{"name":"External API","tags":["Sheets","Mini Pages"]}],"info":{"title":"Sheetfront Public API","version":"0.1.0","x-sheetfront-environment":"production","x-sheetfront-commit":"bec0fe187190","x-sheetfront-build-time":"2026-06-14T02:03:24Z","description":"Transform Google Sheets into powerful REST APIs.\n\n## Limits & Quotas\n\n### Request Limits\n| Limit | Value | Error Code |\n|-------|-------|------------|\n| Request body size | 1 MB | 413 |\n| Cell character limit | 50,000 chars | 400 |\n| Google API timeout | 30 seconds | 504 |\n\n### Pagination\n| Parameter | Default | Maximum |\n|-----------|---------|---------|\n| page_size | 50 rows per connection | 500 rows |\n\n### Rate Limits\n| Scope | Limit | Reset | Header |\n|-------|-------|-------|--------|\n| Per API key (burst) | 45 requests/minute | Rolling window | `X-RateLimit-*-Minute` |\n| Per Google account | 50 requests/minute | Rolling window | `X-RateLimit-*-Account` |\n| Per API key (daily) | Configurable daily limit | Midnight UTC | `X-RateLimit-*-Daily` |\n| Per user (monthly) | Based on plan tier | Billing period | `X-RateLimit-*-Monthly` |\n\n**Note:** The per-account limit is shared across all API keys that access sheets connected to the same Google account. This prevents circumventing Google Sheets API quotas by creating multiple API keys.\n\n## Data Behavior\n\n| Behavior | Description |\n|----------|-------------|\n| Header row | Row 1 is always treated as headers. Data starts at row 2. |\n| Empty cells | Returns `null` in JSON responses |\n| Data types | API keys can return typed JSON values or strings. Free plans always receive strings. |\n| Tab selection | Uses `gid` parameter from sheet URL, or first tab if not specified |\n| Formula protection | Values starting with `=`, `+`, `-`, `@` are escaped with `'` prefix on write to prevent injection |\n\n## Authentication\n\nAll public API endpoints require an API key passed in the `Authorization` header as a bearer token.\n\n### Getting an API Key\n1. Sign in to the dashboard with Google OAuth\n2. Connect a Google Sheet\n3. Generate an API key for the connection\n\n### Using the API Key\n```\nAuthorization: Bearer sf_live_your_api_key_here\n```\n\n### API Key Format\n- **Prefix:** `sf_live_`\n- **Scope:** One API key per sheet connection\n- **Rate limits:** 45 req/min per key, 50 req/min per Google account (shared), configurable daily limit\n\n### Rate Limit Response Headers\nAll public API responses include burst, account, and monthly rate limit headers:\n- `X-RateLimit-Limit-Minute` / `X-RateLimit-Remaining-Minute` / `X-RateLimit-Reset-Minute`\n- `X-RateLimit-Limit-Account` / `X-RateLimit-Remaining-Account` / `X-RateLimit-Reset-Account`\n- `X-RateLimit-Limit-Monthly` / `X-RateLimit-Remaining-Monthly` / `X-RateLimit-Reset-Monthly`\n\nIf an API key has a daily request limit configured, responses also include:\n- `X-RateLimit-Limit-Daily` / `X-RateLimit-Remaining-Daily` / `X-RateLimit-Reset-Daily`\n\nWhen rate limited, the response includes `Retry-After` header with seconds until reset.\n"},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"API key authentication for external developers. Use your generated API key with 'Bearer' prefix."}}},"paths":{"/api/v1/sheets/{id}/data":{"get":{"tags":["Sheets"],"summary":"Read sheet data","description":"Read data from a connected Google Sheet using API key authentication.\n\n**Filtering:** Use field slugs with operator suffixes. Get slugs from `GET /api/v1/sheets/{id}`:\n- `field_eq` - equals\n- `field_ne` - not equals\n- `field_gt` - greater than\n- `field_gte` - greater than or equal\n- `field_lt` - less than\n- `field_lte` - less than or equal\n- `field_contains` - case-insensitive substring match\n\n**Sorting:** `sort=field_slug:asc,another_field:desc`\n\n**Field Selection:** `fields=field_slug,another_field`\n\n**Example:** `?status_eq=active&created_at_gte=2026-01-01&sort=created_at:desc`","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"},{"schema":{"type":"integer","minimum":1,"default":1,"description":"Page number for pagination (1-indexed)","example":1},"required":false,"description":"Page number for pagination (1-indexed)","name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"description":"Number of rows per page. Uses connection default if not specified.","example":100},"required":false,"description":"Number of rows per page. Uses connection default if not specified.","name":"page_size","in":"query"},{"schema":{"type":"string","description":"Sort by field slugs from this sheet, in field:direction format. Get slugs from GET /api/v1/sheets/{id}. Direction can be 'asc' or 'desc'."},"required":false,"description":"Sort by field slugs from this sheet, in field:direction format. Get slugs from GET /api/v1/sheets/{id}. Direction can be 'asc' or 'desc'.","name":"sort","in":"query"},{"schema":{"type":"string","description":"Comma-separated field slugs to include in the response. Get slugs from GET /api/v1/sheets/{id}. Returns all visible fields if not specified."},"required":false,"description":"Comma-separated field slugs to include in the response. Get slugs from GET /api/v1/sheets/{id}. Returns all visible fields if not specified.","name":"fields","in":"query"}],"responses":{"200":{"description":"Sheet data retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":{}}},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"},"page":{"type":"integer","exclusiveMinimum":0},"page_size":{"type":"integer","minimum":1,"maximum":500},"total":{"type":"integer","minimum":0},"total_pages":{"type":"integer","minimum":0},"has_more":{"type":"boolean"},"truncated":{"type":"boolean"},"truncated_warning":{"type":"string"}},"required":["request_id","page","page_size","total","total_pages","has_more"]},"schema":{"type":"object","properties":{"sheet_name":{"type":"string"},"range":{"type":"string"},"headers":{"type":"array","items":{"type":"string"}},"updated_at":{"type":"string","format":"date-time"},"drift":{"type":["object","null"],"properties":{"detected":{"type":["boolean","null"]},"missing":{"type":"array","items":{"type":"string"}},"added":{"type":"array","items":{"type":"string"}},"type_changed":{"type":"array","items":{"type":"object","properties":{"column":{"type":"string"},"old_type":{"type":"string"},"new_type":{"type":"string"}},"required":["column","old_type","new_type"]}},"hint":{"type":"string"},"error":{"type":"string"}},"required":["detected"]}},"required":["sheet_name","range","headers","updated_at","drift"]}},"required":["data","meta","schema"]}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid field \"unknown\". Use one of: name, status, price"},"meta":{"request_id":"req_abc123"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"UNAUTHORIZED","message":"API key is required"},"meta":{"request_id":"req_abc123"}}}}},"403":{"description":"API key not authorized for this sheet connection","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"PERMISSION_DENIED","message":"API key not valid for this sheet"},"meta":{"request_id":"req_abc123"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Daily rate limit exceeded. Resets at midnight UTC."},"meta":{"request_id":"req_abc123"}}}}},"502":{"description":"Google Sheets API error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"GOOGLE_API_ERROR","message":"Failed to fetch sheet data"},"meta":{"request_id":"req_abc123"}}}}}}},"post":{"tags":["Sheets"],"summary":"Append row to sheet","description":"Append a new row to a connected Google Sheet using API key authentication","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":{},"description":"Row data as key-value pairs. Use visible column headers or field slugs as keys. Values starting with =, +, -, @ are automatically escaped to prevent formula injection. Maximum 50,000 characters per cell.","example":{"Name":"John Doe","Email":"john@example.com","Score":"95"}}},"required":["data"]}}}},"responses":{"201":{"description":"Row appended successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"updated_range":{"type":"string"},"updated_rows":{"type":"number"}},"required":["updated_range","updated_rows"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"examples":{"emptyBody":{"summary":"Empty or invalid body","value":{"error":{"code":"VALIDATION_ERROR","message":"Request body must be a valid JSON object with column values"},"meta":{"request_id":"req_abc123"}}},"cellTooLong":{"summary":"Cell exceeds character limit","value":{"error":{"code":"VALIDATION_ERROR","message":"Cell value exceeds maximum length of 50000 characters (got 51234)"},"meta":{"request_id":"req_abc123"}}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"UNAUTHORIZED","message":"API key is required"},"meta":{"request_id":"req_abc123"}}}}},"403":{"description":"API key not authorized for this sheet connection","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"413":{"description":"Request body too large","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"PAYLOAD_TOO_LARGE","message":"Request body too large. Maximum size is 1MB."},"meta":{"request_id":"req_abc123"}}}}},"429":{"description":"Rate limit exceeded or Google write capacity temporarily busy","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"examples":{"dailyLimit":{"summary":"Daily API key limit exceeded","value":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Daily rate limit exceeded. Resets at midnight UTC."},"meta":{"request_id":"req_abc123"}}},"googleWriteCapacity":{"summary":"Google write capacity busy","value":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Google write capacity is temporarily busy. Please retry shortly.","details":{"retryAfter":60}},"meta":{"request_id":"req_abc123"}}}}}}},"502":{"description":"Google Sheets API error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"GOOGLE_API_ERROR","message":"Failed to append row"},"meta":{"request_id":"req_abc123"}}}}}}},"put":{"tags":["Sheets"],"summary":"Update row in sheet","description":"Update an existing row in a connected Google Sheet using API key authentication. Requires an identifier column to be configured.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"description":"A real value from the configured identifier column, not the row number. Example identifier values might look like 'ORD-5001' or 'user@example.com'."},"required":true,"description":"A real value from the configured identifier column, not the row number. Example identifier values might look like 'ORD-5001' or 'user@example.com'.","name":"row_id","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":{},"description":"Partial row data as key-value pairs. Use visible column headers or field slugs as keys. Only provided fields are updated. Values starting with =, +, -, @ are automatically escaped.","example":{"status":"shipped","notes":"Updated tracking info"}}},"required":["data"]}}}},"responses":{"200":{"description":"Row updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"updated_range":{"type":"string"},"updated_cells":{"type":"number"}},"required":["updated_range","updated_cells"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"400":{"description":"Validation error or identifier column not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"examples":{"noIdentifier":{"summary":"No identifier column configured","value":{"error":{"code":"VALIDATION_ERROR","message":"Identifier column not configured for this connection. Set it in the dashboard settings to use PUT/DELETE operations."},"meta":{"request_id":"req_abc123"}}},"invalidField":{"summary":"Invalid field name","value":{"error":{"code":"VALIDATION_ERROR","message":"Invalid field(s): unknown_field"},"meta":{"request_id":"req_abc123"}}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"403":{"description":"API key not authorized or lacks UPDATE permission","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"404":{"description":"Row not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"NOT_FOUND","message":"Row with identifier 'ORD-5001' not found"},"meta":{"request_id":"req_abc123"}}}}},"409":{"description":"Multiple rows match the identifier","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"CONFLICT","message":"Multiple rows (2) match identifier 'ORD-5001'. Identifier column must have unique values."},"meta":{"request_id":"req_abc123"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"502":{"description":"Google Sheets API error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}}}},"delete":{"tags":["Sheets"],"summary":"Delete row from sheet","description":"Delete an existing row from a connected Google Sheet using API key authentication. Requires an identifier column to be configured.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"description":"A real value from the configured identifier column, not the row number. Example identifier values might look like 'ORD-5001' or 'user@example.com'."},"required":true,"description":"A real value from the configured identifier column, not the row number. Example identifier values might look like 'ORD-5001' or 'user@example.com'.","name":"row_id","in":"query"}],"responses":{"200":{"description":"Row deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"deleted_row":{"type":"number"}},"required":["deleted_row"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"400":{"description":"Validation error or identifier column not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"VALIDATION_ERROR","message":"Identifier column not configured for this connection"},"meta":{"request_id":"req_abc123"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"403":{"description":"API key not authorized or lacks DELETE permission","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"404":{"description":"Row not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"NOT_FOUND","message":"Row with identifier 'ORD-5001' not found"},"meta":{"request_id":"req_abc123"}}}}},"409":{"description":"Multiple rows match the identifier","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"CONFLICT","message":"Multiple rows (2) match identifier 'ORD-5001'. Identifier column must have unique values."},"meta":{"request_id":"req_abc123"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"502":{"description":"Google Sheets API error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}}}}},"/api/v1/sheets/{id}/data/bulk":{"post":{"tags":["Sheets"],"summary":"Bulk append rows to sheet","description":"Append multiple rows (1-100) to a connected Google Sheet in a single API call","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","additionalProperties":{}},"minItems":1,"maxItems":100,"description":"Array of row objects (1-100 rows). Use visible column headers or field slugs as keys. Hidden fields are ignored, and each row must include at least one visible value.","example":[{"Name":"John Doe","Email":"john@example.com","Score":"95"},{"Name":"Jane Smith","Email":"jane@example.com","Score":"87"}]}},"required":["rows"]}}}},"responses":{"201":{"description":"At least one row appended successfully (may include partial failures)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"inserted":{"type":"number","description":"Number of rows successfully inserted"},"failed":{"type":"number","description":"Number of rows that failed validation"},"updated_range":{"type":"string","description":"Range updated in Google Sheets (e.g., Sheet1!A2:D51)"},"errors":{"type":"array","items":{"type":"object","properties":{"index":{"type":"number","description":"0-based index of failed row in request"},"field":{"type":"string","description":"Field that caused the error (if applicable)"},"error":{"type":"string","description":"Error message"}},"required":["index","error"]},"description":"Errors for rows that failed validation"}},"required":["inserted","failed","updated_range","errors"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"400":{"description":"All rows failed validation or invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"403":{"description":"Permission denied or sheet access revoked","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"429":{"description":"Google write capacity temporarily busy","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"502":{"description":"Google Sheets API error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}}}}},"/api/v1/sheets/{id}/cache/invalidate":{"post":{"tags":["Sheets"],"summary":"Invalidate cache","description":"Manually invalidate the cache for this sheet connection. The next read request will fetch fresh data from Google Sheets.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"}],"responses":{"200":{"description":"Cache invalidated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"invalidated":{"type":"boolean"},"keys_cleared":{"type":"number"}},"required":["invalidated","keys_cleared"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}},"403":{"description":"API key not authorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]}}}}}}},"/api/v1/sheets/{id}/refresh-schema":{"post":{"tags":["Sheets"],"summary":"Refresh sheet schema","description":"Refresh the cached schema headers for a connected sheet","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"}],"responses":{"200":{"description":"Schema refreshed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"headers":{"type":"array","items":{"type":"string"}},"updated_at":{"type":"string","format":"date-time"}},"required":["headers","updated_at"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"VALIDATION_ERROR","message":"Duplicate headers detected in Google Sheet"},"meta":{"request_id":"req_abc123"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"UNAUTHORIZED","message":"API key is required"},"meta":{"request_id":"req_abc123"}}}}},"403":{"description":"API key not authorized for this sheet connection","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"PERMISSION_DENIED","message":"API key not valid for this sheet"},"meta":{"request_id":"req_abc123"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Daily rate limit exceeded. Resets at midnight UTC."},"meta":{"request_id":"req_abc123"}}}}},"502":{"description":"Google Sheets API error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"GOOGLE_API_ERROR","message":"Failed to refresh schema"},"meta":{"request_id":"req_abc123"}}}}}}}},"/api/v1/sheets/{id}":{"get":{"tags":["Sheets"],"summary":"Get sheet connection details","description":"Get schema headers and metadata for a sheet connection. Use check_drift=true to verify if Google Sheets headers have changed.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"UUID of the sheet connection"},"required":true,"description":"UUID of the sheet connection","name":"id","in":"path"},{"schema":{"type":"string","enum":["true","false"],"default":"false","description":"If true, checks Google Sheets for schema drift (makes additional API call)"},"required":false,"description":"If true, checks Google Sheets for schema drift (makes additional API call)","name":"check_drift","in":"query"}],"responses":{"200":{"description":"Sheet connection details","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"google_sheet_id":{"type":"string"},"sheet_name":{"type":"string"},"sheet_url":{"type":"string","format":"uri"},"tab_gid":{"type":"integer","minimum":0},"tab_title":{"type":"string"},"schema_headers":{"type":"array","items":{"type":"string"}},"schema_updated_at":{"type":["string","null"],"format":"date-time"},"default_page_size":{"type":"integer","minimum":1,"maximum":500},"cache_ttl_seconds":{"type":"integer","minimum":60,"maximum":3600},"identifier_column":{"type":["string","null"]},"emoji":{"type":["string","null"]},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_synced_at":{"type":["string","null"],"format":"date-time"},"drift":{"type":["object","null"],"properties":{"detected":{"type":["boolean","null"]},"missing":{"type":"array","items":{"type":"string"}},"added":{"type":"array","items":{"type":"string"}},"type_changed":{"type":"array","items":{"type":"object","properties":{"column":{"type":"string"},"old_type":{"type":"string"},"new_type":{"type":"string"}},"required":["column","old_type","new_type"]}},"hint":{"type":"string"},"error":{"type":"string"}},"required":["detected"]},"slugs":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of column slugs to original column names. Use slugs in query params for filtering, sorting, and field selection.","example":{"name":"Name","status":"Status","price":"Price"}}},"required":["id","google_sheet_id","sheet_name","sheet_url","tab_gid","tab_title","schema_headers","schema_updated_at","default_page_size","cache_ttl_seconds","identifier_column","emoji","is_active","created_at","updated_at","last_synced_at","drift","slugs"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"UNAUTHORIZED","message":"API key is required"},"meta":{"request_id":"req_abc123"}}}}},"403":{"description":"API key not authorized for this sheet connection","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"PERMISSION_DENIED","message":"API key not valid for this sheet"},"meta":{"request_id":"req_abc123"}}}}},"404":{"description":"Sheet connection not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"NOT_FOUND","message":"Sheet connection not found"},"meta":{"request_id":"req_abc123"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["error","meta"]},"example":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Daily rate limit exceeded. Resets at midnight UTC."},"meta":{"request_id":"req_abc123"}}}}}}}},"/p/{slug}":{"get":{"tags":["Mini Pages"],"summary":"Get public mini page","description":"Fetch a published sheet-powered mini page by slug.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"slug","in":"path"},{"schema":{"type":"integer","minimum":1,"default":1,"description":"Page number for pagination (1-indexed).","example":1},"required":false,"description":"Page number for pagination (1-indexed).","name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"description":"Number of rows per page.","example":25},"required":false,"description":"Number of rows per page.","name":"page_size","in":"query"},{"schema":{"type":"string","description":"Search visible row values before pagination."},"required":false,"description":"Search visible row values before pagination.","name":"search","in":"query"},{"schema":{"type":"string","description":"Filter by the mapped category field before pagination. If no category is mapped, uses an exchange or market column when present."},"required":false,"description":"Filter by the mapped category field before pagination. If no category is mapped, uses an exchange or market column when present.","name":"filter","in":"query"},{"schema":{"type":"string","enum":["default","title-asc","title-desc","ticker-asc"],"default":"default","description":"Sort rows before pagination. Title sort uses the mapped title field. Ticker sort uses a ticker or symbol column when present."},"required":false,"description":"Sort rows before pagination. Title sort uses the mapped title field. Ticker sort uses a ticker or symbol column when present.","name":"sort","in":"query"}],"responses":{"200":{"description":"Mini page details","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"type":{"type":"string","enum":["directory"]},"field_mapping":{"type":"object","properties":{"title":{"type":["string","null"]},"description":{"type":["string","null"]},"image":{"type":["string","null"]},"primaryLink":{"type":["string","null"]},"category":{"type":["string","null"]},"status":{"type":["string","null"]},"metadata":{"type":"array","items":{"type":"string"}}},"required":["title","description","image","primaryLink","category","status","metadata"]},"rows":{"type":"array","items":{"type":"object","additionalProperties":{}}},"settings":{"type":"object","properties":{"defaultView":{"type":"string","enum":["grid","list"]},"descriptionOverride":{"type":["string","null"],"maxLength":280},"featuredRowIndex":{"type":"integer","minimum":0},"featuredRowMode":{"type":"string","enum":["first","manual","none"]},"layout":{"type":"string","enum":["auto","cards","list","table","gallery","compact"]},"pageType":{"type":"string","enum":["auto","company","person","product","event","job","resource","location","generic"]},"logoUrl":{"type":["string","null"],"format":"uri"},"showPoweredByBadge":{"type":"boolean"}}},"show_powered_by_badge":{"type":"boolean"},"filter_options":{"type":"array","items":{"type":"string"}},"pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1},"total":{"type":"integer","minimum":0},"totalPages":{"type":"integer","minimum":0},"hasMore":{"type":"boolean"}},"required":["page","pageSize","total","totalPages","hasMore"]}},"required":["id","name","slug","type","field_mapping","rows","settings","show_powered_by_badge"]},"meta":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"}},"required":["request_id"]}},"required":["data","meta"]}}}},"404":{"description":"Mini page not found or not published"},"503":{"description":"Mini page temporarily unavailable"}}}}},"webhooks":{},"servers":[{"url":"https://api.sheetfront.com","description":"Production API"}]}