openapi: 3.1.0 info: title: TypoSuggest API version: 1.0.0 summary: Sub-millisecond typo-tolerant autocomplete and instant search engine API. description: | TypoSuggest (`https://typosuggest.pial.digital`) is a multi-tenant, in-memory autocomplete and prefix-search engine written in Go. It provides a 4-stage retrieval pipeline (Direct Prefix Lookup, UAX #29 Multi-Token Inverted Index, SymSpell + Damerau-Levenshtein Typo Resolution, and Progressive Right-to-Left Token Fallback), Zstandard binary snapshots, and a native Model Context Protocol (MCP) server at `/mcp`. ### Authentication & Key Security - **REST Endpoints (`/v1/*`)**: - Pass API keys (`ac_pub_...` for read-only `GET /v1/suggest` or `ac_sec_...` for dataset/item management) via the `X-API-Key` HTTP header. - For browser `GET /v1/suggest` calls, a read-only public key (`ac_pub_...`) may optionally be passed via `?key=ac_pub_...` (alias `?api_key=`) to avoid CORS `OPTIONS` preflight round-trips. Because URL query parameters can be recorded in proxy/CDN logs, browser history, and monitoring systems, **only use `?key=` with public read-only keys (`ac_pub_...`) and never pass secret keys (`ac_sec_...`) or JWTs in URLs**. - Pass Dashboard session JWTs via `Authorization: Bearer `. Do **not** pass `ac_pub_...` or `ac_sec_...` keys inside `Authorization: Bearer` on `/v1/*` routes. - **MCP Endpoint (`/mcp`)**: - Accepts API keys or JWTs via `X-API-Key` or `Authorization: Bearer`. contact: name: TypoSuggest Support url: https://typosuggest.pial.digital servers: - url: https://typosuggest.pial.digital description: Production Server (ap-southeast-2) tags: - name: Search description: Sub-millisecond typo-tolerant autocomplete search - name: Datasets description: Dataset creation, stats, reset, seed, and deletion - name: Items description: Batch ingestion, atomic updates, and deletion of dataset items - name: MCP description: Model Context Protocol (JSON-RPC 2.0) server for AI agents - name: Auth description: Tenant account registration, email verification, and login - name: Dashboard description: Tenant overview and API key management - name: System description: Health checks, playground demo session, and contact inquiries components: securitySchemes: ApiKeyHeader: type: apiKey in: header name: X-API-Key description: Public (`ac_pub_...`, read-only on `GET /v1/suggest`) or Secret (`ac_sec_...`, full access) API key. ApiKeyQuery: type: apiKey in: query name: key description: | Read-only public API key (`ac_pub_...`) passed as `?key=ac_pub_...` (or `?api_key=`) on `GET /v1/suggest` to avoid browser CORS preflight. WARNING: Query parameters can be recorded in server logs and monitoring systems. Use ONLY with public read-only keys (`ac_pub_...`); NEVER pass secret keys (`ac_sec_...`) in URLs. BearerJWT: type: http scheme: bearer bearerFormat: JWT description: 30-day session JWT issued by `/v1/auth/register`, `/v1/auth/login`, or Google OAuth (also accepts API keys on `/mcp`). responses: BadRequest: description: Invalid request parameters or malformed JSON payload. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Query parameter 'dataset_id' is required" Unauthorized: description: Missing or invalid API key or Bearer JWT token. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Missing required authentication: provide X-API-Key or Authorization: Bearer " PaymentRequired: description: Tenant subscription quota exceeded (Beta defaults — 2 datasets, 500 total rows, 10,000 monthly searches). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "monthly search quota of 10000 queries exceeded; please upgrade your plan" Forbidden: description: Insufficient key permissions (e.g. using `ac_pub_...` on a write route) or cross-tenant dataset access. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "public search-only key cannot perform write operations; use your ac_sec_ secret key" NotFound: description: Requested dataset or resource does not exist. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Dataset not found" TooManyRequests: description: Token-bucket rate limit exceeded. headers: Retry-After: schema: type: integer example: 1 description: Seconds to wait before retrying. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "rate limit exceeded, please slow down" InternalServerError: description: Unexpected internal server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: "Internal server error" schemas: ErrorResponse: type: object required: - error properties: error: type: string description: Human-readable error message. Suggestion: type: object required: - text - group - score - frequency properties: text: type: string description: Display text of the deduplicated matched phrase or n-gram. example: "modern wordpress website" group: type: string description: Category or group tag assigned during ingestion. example: "gig" score: type: number format: double description: Deterministic ranking score rounded to 1 decimal place. example: 97.4 frequency: type: integer format: int64 minimum: 1 description: Cumulative RefCount frequency across ingested documents. example: 42 SuggestResponse: type: object required: - query - took_ms - suggestions properties: query: type: string description: Echoed search query string. example: "wrodpres" took_ms: type: number format: double description: Server-side in-memory engine execution time in milliseconds. example: 0.142 suggestions: type: array items: $ref: '#/components/schemas/Suggestion' Dataset: type: object required: - id - tenant_id - name - typo_tolerance - boilerplate_regex - group_caps - max_ngram_words - created_at - updated_at properties: id: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" tenant_id: type: string format: uuid example: "2c9d8f10-91b2-4a88-b822-112233445566" name: type: string example: "Production Store Catalog" typo_tolerance: type: boolean example: true boilerplate_regex: type: string example: "(?i)^i\\s+will\\s+(be\\s+(your\\s+)?|do\\s+)?" group_caps: type: object additionalProperties: type: integer example: category: 2 max_ngram_words: type: integer minimum: 1 maximum: 10 example: 4 created_at: type: string format: date-time updated_at: type: string format: date-time CreateDatasetRequest: type: object required: - name properties: name: type: string description: Human-readable dataset name. example: "Production Store Catalog" typo_tolerance: type: boolean default: true description: Enable Stage 3 SymSpell + Damerau-Levenshtein typo tolerance. boilerplate_regex: type: string description: Optional regex stripped from the start of ingested items (defaults to `(?i)^i\s+will\s+(be\s+(your\s+)?|do\s+)?`). max_ngram_words: type: integer minimum: 1 maximum: 10 default: 4 description: Max word count for extracted leading n-gram sub-phrases (`1..10`). group_caps: type: object additionalProperties: type: integer description: Optional per-group result limits enforced at query time. example: category: 2 CreateDatasetResponse: type: object required: - dataset properties: dataset: $ref: '#/components/schemas/Dataset' DatasetStatsResponse: type: object required: - dataset_id - total_rows_ingested - active_unique_phrases - unique_dictionary_tokens - symspell_keys - queries_served - avg_query_latency_ms - snapshot_size_bytes properties: dataset_id: type: string format: uuid total_rows_ingested: type: integer format: int64 example: 150 active_unique_phrases: type: integer example: 412 unique_dictionary_tokens: type: integer example: 285 symspell_keys: type: integer example: 1840 queries_served: type: integer format: int64 example: 1024 avg_query_latency_ms: type: number format: double example: 0.084 snapshot_size_bytes: type: integer format: int64 example: 18432 IngestItem: type: object required: - text properties: id: type: string description: Optional caller-defined item identifier used in FNV-1a document deduplication. example: "sku-101" text: type: string description: Text phrase or title to index. example: "Apple MacBook Pro 16 inch M3 Max" group: type: string default: "item" description: Category or group label (defaults to `"item"`). example: "product" count: type: integer minimum: 1 default: 1 description: Optional initial popularity weight added to phrase `RefCount` (defaults to `1`). example: 25 IngestRequest: type: object description: Provide `items`, `texts`, or both (at least one non-empty entry is required; max 25,000 total items per call). properties: items: type: array maxItems: 25000 items: $ref: '#/components/schemas/IngestItem' texts: type: array maxItems: 25000 items: type: string description: Optional array of plain strings (indexed with `group = "item"` and `count = 1`). IngestResponse: type: object required: - added - skipped_duplicates - active_phrases - took_ms properties: added: type: integer example: 3 skipped_duplicates: type: integer example: 0 active_phrases: type: integer example: 24 took_ms: type: number format: double example: 1.18 DeleteRequest: type: object description: Provide `ids`, `texts`, or both (max 25,000 entries per call). properties: ids: type: array maxItems: 25000 items: type: string example: ["sku-101"] texts: type: array maxItems: 25000 items: type: string example: ["Apple MacBook Pro 16 inch M3 Max"] DeleteResponse: type: object required: - deleted_docs - purged_phrases - active_phrases - took_ms properties: deleted_docs: type: integer example: 1 purged_phrases: type: integer example: 4 active_phrases: type: integer example: 20 took_ms: type: number format: double example: 0.31 UpdateItemRequest: type: object required: - id - new_text properties: id: type: string description: Document ID to update. example: "sku-101" old_text: type: string description: Optional previous text string. example: "Apple MacBook Pro 16 inch M3" new_text: type: string description: Replacement text string to index. example: "Apple MacBook Pro 16 inch M3 Max" text: type: string description: Optional alias for `new_text`. group: type: string example: "product" UpdateItemResponse: type: object required: - success - active_phrases - took_ms properties: success: type: boolean example: true active_phrases: type: integer example: 24 took_ms: type: number format: double example: 0.55 ResetDatasetResponse: type: object required: - dataset_id - status - active_unique_phrases - total_rows_ingested properties: dataset_id: type: string format: uuid status: type: string example: "reset_success" active_unique_phrases: type: integer example: 0 total_rows_ingested: type: integer format: int64 example: 0 SeedDatasetRequest: type: object properties: count: type: integer default: 500 maximum: 10000000 reset: type: boolean default: false SeedDatasetResponse: type: object required: - dataset_id - rows_seeded - total_rows_ingested - active_unique_phrases - took_ms - throughput_rows_sec properties: dataset_id: type: string rows_seeded: type: integer total_rows_ingested: type: integer format: int64 active_unique_phrases: type: integer took_ms: type: number format: double throughput_rows_sec: type: number format: double HealthResponse: type: object required: - status - postgres - redis - snapshots properties: status: type: string enum: [ok, degraded] example: "ok" postgres: type: boolean example: true redis: type: boolean example: true snapshots: type: boolean example: true details: type: object additionalProperties: type: string example: postgres: "connected" redis: "connected" snapshots: "writable" DemoSessionResponse: type: object required: - tenant_id - dataset_id - api_key - dataset_name properties: tenant_id: type: string dataset_id: type: string api_key: type: string description: Read-only public API key (`ac_pub_...`) for the live playground. dataset_name: type: string example: "Demo Multi-Domain Dataset" ContactRequest: type: object required: - name - email - message properties: name: type: string example: "Jane Doe" email: type: string format: email example: "jane@example.com" category: type: string example: "Quota Request" message: type: string example: "Interested in a custom high-volume catalog plan." TenantSummary: type: object required: - id - name - email - email_verified - plan_tier - subscription_status - max_datasets - max_rows - max_monthly_queries - monthly_queries_used - quota_reset_at - enforce_paywall properties: id: type: string format: uuid name: type: string email: type: string format: email email_verified: type: boolean plan_tier: type: string example: "indie_beta" subscription_status: type: string example: "active" billing_customer_id: type: string max_datasets: type: integer example: 2 max_rows: type: integer format: int64 example: 500 max_monthly_queries: type: integer format: int64 example: 10000 monthly_queries_used: type: integer format: int64 example: 0 quota_reset_at: type: string format: date-time enforce_paywall: type: boolean RegisterRequest: type: object required: - name - email - password properties: name: type: string example: "Acme Store" email: type: string format: email example: "dev@example.com" password: type: string minLength: 8 RegisterResponse: type: object required: - token - tenant - starter_dataset_id - secret_key - public_key - email_verified - requires_verification properties: token: type: string description: 30-day session JWT. tenant: $ref: '#/components/schemas/TenantSummary' starter_dataset_id: type: string format: uuid secret_key: type: string description: Initial `ac_sec_...` key (shown once). public_key: type: string description: Initial `ac_pub_...` key. email_verified: type: boolean requires_verification: type: boolean LoginRequest: type: object required: - email - password properties: email: type: string format: email password: type: string LoginResponse: type: object required: - token - tenant properties: token: type: string description: 30-day session JWT. tenant: $ref: '#/components/schemas/TenantSummary' VerifyEmailRequest: type: object required: - code properties: code: type: string minLength: 6 maxLength: 6 example: "123456" VerifyEmailResponse: type: object required: - verified - message properties: verified: type: boolean example: true message: type: string example: "Email verified successfully" DashboardOverviewResponse: type: object required: - tenant - usage - datasets - api_keys properties: tenant: $ref: '#/components/schemas/TenantSummary' usage: type: object properties: datasets_used: type: integer max_datasets: type: integer example: 2 total_rows_used: type: integer format: int64 max_rows: type: integer format: int64 example: 500 monthly_queries_used: type: integer format: int64 max_monthly_queries: type: integer format: int64 example: 10000 datasets: type: array items: type: object properties: id: type: string name: type: string typo_tolerance: type: boolean group_caps: type: object additionalProperties: type: integer active_unique_phrases: type: integer total_rows_ingested: type: integer format: int64 queries_served: type: integer format: int64 avg_query_latency_ms: type: number format: double created_at: type: string format: date-time api_keys: type: array items: type: object properties: id: type: string name: type: string role: type: string enum: [public, secret] key_prefix: type: string created_at: type: string format: date-time CreateKeyRequest: type: object required: - name properties: name: type: string example: "Storefront Public Key" role: type: string enum: [public, secret] default: secret CreateKeyResponse: type: object required: - id - name - role - key_prefix - raw_key - created_at properties: id: type: string format: uuid name: type: string role: type: string enum: [public, secret] key_prefix: type: string raw_key: type: string description: Full plaintext key (`ac_pub_...` or `ac_sec_...`), shown only once at creation time. created_at: type: string format: date-time MCPRequest: type: object required: - jsonrpc - method properties: jsonrpc: type: string const: "2.0" id: oneOf: - type: string - type: integer - type: "null" method: type: string enum: [initialize, notifications/initialized, ping, tools/list, tools/call] params: type: object additionalProperties: true MCPResponse: type: object required: - jsonrpc properties: jsonrpc: type: string const: "2.0" id: oneOf: - type: string - type: integer - type: "null" result: type: object additionalProperties: true error: type: object properties: code: type: integer message: type: string paths: /v1/suggest: get: tags: [Search] summary: Query typo-tolerant autocomplete suggestions description: | Executes the 4-stage autocomplete pipeline (Prefix Lookup -> UAX #29 Multi-Token Inverted Index -> SymSpell + Damerau-Levenshtein Typo Resolution -> Progressive Right-to-Left Token Fallback). Accessible with a read-only public key (`ac_pub_...`), secret key (`ac_sec_...`), or Dashboard JWT. security: - ApiKeyHeader: [] - ApiKeyQuery: [] - BearerJWT: [] parameters: - name: dataset_id in: query required: true schema: type: string description: Target dataset UUID. - name: q in: query required: false schema: type: string maxLength: 100 description: Search query string (alias `query`). Empty string returns top phrases by popularity. - name: query in: query required: false schema: type: string maxLength: 100 description: Fallback alias for `q`. - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 20 default: 8 description: Maximum number of suggestions to return (`1..20`, default `8`). - name: typo in: query required: false schema: type: boolean description: Optional per-request override for dataset `typo_tolerance` (`true`/`1` or `false`/`0`). responses: '200': description: Autocomplete suggestions returned in sub-millisecond engine time. content: application/json: schema: $ref: '#/components/schemas/SuggestResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /v1/datasets: post: tags: [Datasets] summary: Create a new dataset description: Creates a new dataset in PostgreSQL and initializes its in-memory `DatasetIndex`. Requires a secret key (`ac_sec_...`) in `X-API-Key` or a Bearer JWT. security: - ApiKeyHeader: [] - BearerJWT: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateDatasetRequest' responses: '201': description: Dataset created. content: application/json: schema: $ref: '#/components/schemas/CreateDatasetResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/Forbidden' /v1/datasets/{dataset_id}: delete: tags: [Datasets] summary: Delete a dataset description: Permanently deletes a dataset from PostgreSQL, evicts its in-memory index, clears its Redis ZSET, and deletes its `.idx` snapshot file. Requires a secret key (`ac_sec_...`) or Bearer JWT. security: - ApiKeyHeader: [] - BearerJWT: [] parameters: - name: dataset_id in: path required: true schema: type: string responses: '200': description: Dataset deleted successfully. content: application/json: schema: type: object required: [status, dataset_id] properties: status: type: string example: "deleted" dataset_id: type: string '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /v1/datasets/{dataset_id}/stats: get: tags: [Datasets] summary: Get real-time dataset metrics description: Returns live in-memory counters, SymSpell key counts, average query latency, and `.idx` snapshot file size. parameters: - name: dataset_id in: path required: true schema: type: string responses: '200': description: Real-time dataset statistics. content: application/json: schema: $ref: '#/components/schemas/DatasetStatsResponse' '404': $ref: '#/components/responses/NotFound' /v1/datasets/{dataset_id}/reset: post: tags: [Datasets] summary: Reset dataset index description: Wipes all indexed items, phrases, Redis sorted-set entries, and `.idx` disk snapshot while keeping the dataset configuration intact. Requires a secret key (`ac_sec_...`) or Bearer JWT. security: - ApiKeyHeader: [] - BearerJWT: [] parameters: - name: dataset_id in: path required: true schema: type: string responses: '200': description: Dataset reset completed. content: application/json: schema: $ref: '#/components/schemas/ResetDatasetResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /v1/datasets/{dataset_id}/items:add: post: tags: [Items] summary: Batch ingest items description: Normalizes, deduplicates, and indexes up to 25,000 items (`10 MB` max body) into the dataset and triggers an asynchronous Zstandard `.idx` snapshot save. Requires a secret key (`ac_sec_...`) or Bearer JWT. security: - ApiKeyHeader: [] - BearerJWT: [] parameters: - name: dataset_id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/IngestRequest' responses: '200': description: Items ingested and indexed. content: application/json: schema: $ref: '#/components/schemas/IngestResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /v1/datasets/{dataset_id}/items:delete: post: tags: [Items] summary: Batch delete items description: Deletes up to 25,000 items by `ids` and/or `texts`, decrementing associated phrase `RefCount` values. Requires a secret key (`ac_sec_...`) or Bearer JWT. security: - ApiKeyHeader: [] - BearerJWT: [] parameters: - name: dataset_id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeleteRequest' responses: '200': description: Items deleted from index. content: application/json: schema: $ref: '#/components/schemas/DeleteResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /v1/datasets/{dataset_id}/items:update: put: tags: [Items] summary: Atomically update an item description: Atomically removes old phrases for `id` and indexes `new_text` under a single index write lock. Requires a secret key (`ac_sec_...`) or Bearer JWT. security: - ApiKeyHeader: [] - BearerJWT: [] parameters: - name: dataset_id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateItemRequest' responses: '200': description: Item updated. content: application/json: schema: $ref: '#/components/schemas/UpdateItemResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /v1/datasets/{dataset_id}/seed: post: tags: [Datasets] summary: Seed synthetic multi-domain items (Founder Admin only in production) description: Seeds realistic multi-domain items in parallel. Restricted to founder superadmin credentials when `APP_ENV=production`. security: - ApiKeyHeader: [] - BearerJWT: [] parameters: - name: dataset_id in: path required: true schema: type: string requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/SeedDatasetRequest' responses: '200': description: Synthetic rows seeded. content: application/json: schema: $ref: '#/components/schemas/SeedDatasetResponse' '403': $ref: '#/components/responses/Forbidden' /mcp: get: tags: [MCP] summary: MCP server discovery metadata description: Returns server metadata, supported protocol version (`2024-11-05`), and authentication instructions. responses: '200': description: MCP discovery payload. content: application/json: schema: type: object post: tags: [MCP] summary: Model Context Protocol (JSON-RPC 2.0) endpoint description: | Handles MCP JSON-RPC 2.0 methods (`initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`). Tools exposed: `suggest`, `list_datasets`, `get_dataset_stats`, `create_dataset`, `add_items`, `delete_dataset`. security: - ApiKeyHeader: [] - BearerJWT: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MCPRequest' responses: '200': description: JSON-RPC 2.0 response. content: application/json: schema: $ref: '#/components/schemas/MCPResponse' '202': description: Accepted notification (`notifications/initialized`). /health: get: tags: [System] summary: System component health check description: Checks PostgreSQL, Redis, and snapshot disk writeability (cached for 5 seconds with sanitized status labels). responses: '200': description: All subsystems operational. content: application/json: schema: $ref: '#/components/schemas/HealthResponse' '503': description: One or more subsystems degraded. content: application/json: schema: $ref: '#/components/schemas/HealthResponse' /v1/demo-session: get: tags: [System] summary: Get live playground demo session credentials description: Returns the demo `tenant_id`, `dataset_id`, and a read-only `ac_pub_...` key for the interactive landing-page playground. responses: '200': description: Demo session details. content: application/json: schema: $ref: '#/components/schemas/DemoSessionResponse' /v1/contact: post: tags: [System] summary: Submit a contact or custom-plan inquiry requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ContactRequest' responses: '201': description: Inquiry recorded. content: application/json: schema: type: object properties: status: type: string example: "ok" id: type: string message: type: string '400': $ref: '#/components/responses/BadRequest' '429': $ref: '#/components/responses/TooManyRequests' /v1/auth/register: post: tags: [Auth] summary: Register a new tenant account requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterRequest' responses: '201': description: Registration completed; provisions starter dataset, keys, and sends 6-digit email verification code. content: application/json: schema: $ref: '#/components/schemas/RegisterResponse' '400': $ref: '#/components/responses/BadRequest' '409': description: Email already registered. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': $ref: '#/components/responses/TooManyRequests' /v1/auth/verify-email: post: tags: [Auth] summary: Verify 6-digit email OTP code security: - BearerJWT: [] - ApiKeyHeader: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VerifyEmailRequest' responses: '200': description: Email verified successfully. content: application/json: schema: $ref: '#/components/schemas/VerifyEmailResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /v1/auth/resend-code: post: tags: [Auth] summary: Resend 6-digit email verification code security: - BearerJWT: [] - ApiKeyHeader: [] responses: '200': description: Verification code resent. content: application/json: schema: type: object properties: status: type: string message: type: string '401': $ref: '#/components/responses/Unauthorized' /v1/auth/login: post: tags: [Auth] summary: Authenticate with email and password requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LoginRequest' responses: '200': description: Authenticated; returns 30-day Bearer JWT. content: application/json: schema: $ref: '#/components/schemas/LoginResponse' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /v1/auth/google/status: get: tags: [Auth] summary: Check whether Google OAuth 2.0 login is enabled responses: '200': description: Google OAuth status. content: application/json: schema: type: object required: [enabled] properties: enabled: type: boolean /v1/auth/google/login: get: tags: [Auth] summary: Initiate Google OAuth 2.0 login redirect responses: '302': description: Redirects to Google OAuth consent screen. /v1/dashboard/overview: get: tags: [Dashboard] summary: Get tenant dashboard overview description: Returns tenant profile, subscription quotas (`2 datasets`, `500 rows`, `10,000 monthly searches` on Beta), monthly usage, API keys, and datasets. Requires Bearer JWT or secret key (`ac_sec_...`). security: - BearerJWT: [] - ApiKeyHeader: [] responses: '200': description: Tenant dashboard overview payload. content: application/json: schema: $ref: '#/components/schemas/DashboardOverviewResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /v1/dashboard/keys: post: tags: [Dashboard] summary: Issue a new public or secret API key security: - BearerJWT: [] - ApiKeyHeader: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateKeyRequest' responses: '201': description: API key created; `raw_key` is returned once. content: application/json: schema: $ref: '#/components/schemas/CreateKeyResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /v1/dashboard/keys/{key_id}: delete: tags: [Dashboard] summary: Revoke an API key security: - BearerJWT: [] - ApiKeyHeader: [] parameters: - name: key_id in: path required: true schema: type: string format: uuid responses: '200': description: API key revoked. content: application/json: schema: type: object required: [status, key_id] properties: status: type: string example: "revoked" key_id: type: string '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden'