{"openapi":"3.0.3","info":{"title":"Airbrx Admin API","version":"1.0.0","description":"Admin API for managing Airbrx Data Proxy configurations, users, tenants, cache metadata, and AI-powered rule recommendations.\n\n## Authentication\n\nThe API supports multiple authentication methods:\n- **Google OAuth 2.0** - For user authentication via `/login` and `/callback` endpoints\n- **Bearer JWT Token** - Airbrx JWT tokens issued after OAuth authentication\n- **Personal Access Tokens (PAT)** - Bearer tokens for service accounts\n\n## Authorization (RBAC)\n\nRole-Based Access Control is enforced via JWT scopes.\nEach user has a primary role (e.g., `admin`, `user`, `guest`) that determines access to endpoints.","contact":{"name":"Airbrx Support","url":"https://airbrx.com"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.airbrx.ai","description":"Production server"}],"security":[{"BearerAuth":[]},{"OAuth2":[]}],"tags":[{"name":"API","description":"API metadata and discovery"},{"name":"Authentication","description":"OAuth 2.0 authentication flows using Descope (`/auth/login`, `/auth/callback`) supporting multiple identity providers (Google, Office 365)."},{"name":"Users","description":"User management endpoints"},{"name":"Roles","description":"User role management"},{"name":"Tenants","description":"Tenant assignment for users"},{"name":"Configuration","description":"Tenant configuration management"},{"name":"Cache","description":"Cache metadata management"},{"name":"Summaries","description":"Daily and yearly usage summaries"},{"name":"AI","description":"AI-powered rule recommendations"},{"name":"Private Preview","description":"Private preview registration and verification endpoints"},{"name":"Logs","description":"Log summarization and management"},{"name":"Permissions","description":"Delegated permission grant management. Allows admins to grant tenant access to other users with optional delegation rights."},{"name":"Markers","description":"Cache invalidation marker management. Markers signal that cached query results are stale and should be refreshed."},{"name":"Accounts","description":"Account management. Accounts represent billing entities that can own multiple tenants and registered domains."},{"name":"Domains","description":"Domain registration management. Domains are registered to accounts and used to validate FQDN assignments."},{"name":"FQDNs","description":"FQDN (Fully Qualified Domain Name) management. FQDNs are assigned to tenants and used for proxy routing."}],"paths":{"/":{"get":{"tags":["API"],"summary":"Get OpenAPI contract","description":"Returns the OpenAPI 3.0 specification for this API","operationId":"getOpenApiContract","security":[],"responses":{"200":{"description":"OpenAPI contract","content":{"application/json":{"schema":{"type":"object","description":"OpenAPI 3.0 specification"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/auth/login":{"get":{"tags":["Authentication"],"summary":"Initiate Descope login flow","description":"Redirects to Descope hosted authentication page. Supports multiple identity providers (Google, Office 365) configured in Descope dashboard. After user authenticates, Descope redirects to `/auth/callback`.","operationId":"authLogin","security":[],"parameters":[{"name":"redirect","in":"query","description":"URL path to redirect to after successful login","schema":{"type":"string","default":"/"},"example":"/dashboard"},{"name":"return_to","in":"query","description":"Alternative parameter name for redirect path","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to Descope authentication page","headers":{"Location":{"schema":{"type":"string"},"description":"Descope authentication URL"}}},"500":{"description":"Descope not configured"}}}},"/auth/callback":{"get":{"tags":["Authentication"],"summary":"Descope OAuth callback endpoint","description":"Handles Descope callback, validates session token, creates/updates user profile in S3, generates short-lived access token and long-lived refresh token, and redirects to original URL with tokens. Access token expires in 1 hour (configurable via ACCESS_TOKEN_EXPIRY), refresh token expires in 30 days (configurable via REFRESH_TOKEN_EXPIRY). Use POST /oauth/token with grant_type=refresh_token to get new access tokens. Works with any identity provider configured in Descope (Google, Office 365, etc.).","operationId":"authCallback","security":[],"parameters":[{"name":"code","in":"query","required":true,"description":"Authorization code from Descope","schema":{"type":"string"}},{"name":"state","in":"query","description":"Base64-encoded JSON with redirectTo and timestamp","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to original URL with access and refresh tokens","headers":{"Location":{"schema":{"type":"string"},"description":"Original URL with access_token, refresh_token, and expires_in query parameters. Example: https://dashboard.airbrx.com/?access_token=eyJ...&refresh_token=airbrx_refresh_eyJ...&expires_in=3600"}}},"400":{"description":"Missing authorization code"},"401":{"description":"Authentication failed"},"500":{"description":"Descope not configured"}}}},"/users":{"get":{"tags":["Users"],"summary":"List all users","description":"Returns a list of all user objects stored in S3 with HAL-style links.","operationId":"listUsers","responses":{"200":{"description":"List of users with HAL links","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string","example":"/users"}}},"parent":{"type":"object","properties":{"href":{"type":"string","example":"/"}}},"items":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"user@example.com"},"type":{"type":"string","enum":["user"]},"href":{"type":"string","example":"/users/user@example.com"}}}}}}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/users/{email}":{"get":{"tags":["Users"],"summary":"Get user by email","description":"Retrieve complete user profile including roles, tenants, and OAuth data","operationId":"getUser","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Users"],"summary":"Update user profile","description":"Update or create a user profile. Stores directly to S3.","operationId":"updateUser","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"200":{"description":"User updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Users"],"summary":"Delete user","description":"Permanently delete a user profile from S3","operationId":"deleteUser","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"responses":{"200":{"description":"User deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"User user@example.com deleted successfully"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/users/{email}/roles":{"get":{"tags":["Roles"],"summary":"Get user roles","description":"Retrieve the roles object for a user","operationId":"getUserRoles","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"responses":{"200":{"description":"User roles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roles"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Roles"],"summary":"Create or overwrite user roles","description":"Replace the entire roles object for a user","operationId":"createUserRoles","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roles"}}}},"responses":{"200":{"description":"Roles updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roles"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Roles"],"summary":"Merge user roles","description":"Update user roles by merging with existing roles","operationId":"updateUserRoles","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roles"}}}},"responses":{"200":{"description":"Roles merged successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Roles"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/users/{email}/tenants":{"get":{"tags":["Tenants"],"summary":"Get user tenants","description":"Retrieve the list of tenant IDs assigned to a user","operationId":"getUserTenants","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"responses":{"200":{"description":"User tenants","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"example":["demo.app.airbrx.com","acme.app.airbrx.com"]}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Tenants"],"summary":"Create or overwrite user tenants","description":"Replace the entire tenants array for a user","operationId":"createUserTenants","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"example":["demo.app.airbrx.com","acme.app.airbrx.com"]}}}},"responses":{"200":{"description":"Tenants updated successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Tenants"],"summary":"Append tenants to user","description":"Add tenants to existing list (no duplicates)","operationId":"appendUserTenants","parameters":[{"$ref":"#/components/parameters/EmailParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"example":["newclient.app.airbrx.com"]}}}},"responses":{"200":{"description":"Tenants appended successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/tenants":{"get":{"tags":["Configuration"],"summary":"List accessible tenants","description":"Returns a list of tenants the caller has access to with HAL-style links. For PAT-derived access tokens, returns only the tenants specified in the token's `tenantIds` claim. For user JWTs, returns tenants based on user's tenant assignments.","operationId":"listTenants","responses":{"200":{"description":"List of accessible tenants with HAL links","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string","example":"/config/tenants"}}},"parent":{"type":"object","properties":{"href":{"type":"string","example":"/"}}},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"demo.app.airbrx.com"},"href":{"type":"string","example":"/config/tenants/demo.app.airbrx.com"}}}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Configuration"],"summary":"Create a new tenant","description":"Creates a new tenant with a server-generated UUID. Requires a valid accountId and enforces FQDN lifecycle rules from AIR-634.","operationId":"createTenant","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantCreate"}}}},"responses":{"201":{"description":"Tenant created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantConfig"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Account not found"},"409":{"description":"Conflict: FQDN already claimed or tombstoned, or tenant name not unique within account"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/tenants/{tenant}":{"get":{"tags":["Configuration"],"summary":"Get tenant HATEOAS links","description":"Returns HATEOAS-style links to the tenant's config and rules endpoints","operationId":"getTenantLinks","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"responses":{"200":{"description":"Tenant HATEOAS links","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string","example":"/config/tenants/demo.app.airbrx.com"}}},"parent":{"type":"object","properties":{"href":{"type":"string","example":"/config/tenants"}}},"config":{"type":"object","properties":{"href":{"type":"string","example":"/config/tenants/demo.app.airbrx.com/config"}}},"rules":{"type":"object","properties":{"href":{"type":"string","example":"/config/tenants/demo.app.airbrx.com/rules"}}}}},"tenantId":{"type":"string","example":"demo.app.airbrx.com"}}}}}}}},"delete":{"tags":["Configuration"],"summary":"Delete tenant","description":"Permanently delete a tenant and all its configuration files from the admin bucket. This removes the tenant folder and all files underneath (config, rules, etc.). Does not affect tenant data in the SaaS bucket.","operationId":"deleteTenant","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"responses":{"200":{"description":"Tenant deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Tenant demo.app.airbrx.com deleted successfully"},"deletedFiles":{"type":"integer","description":"Number of files deleted","example":2}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/tenants/{tenant}/config":{"get":{"tags":["Configuration"],"summary":"Get tenant configuration","description":"Retrieve the config for a specific tenant","operationId":"getTenantConfig","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"responses":{"200":{"description":"Tenant configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantConfig"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Configuration"],"summary":"Update tenant configuration","description":"Update or create config for a tenant","operationId":"updateTenantConfig","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantConfig"}}}},"responses":{"200":{"description":"Configuration updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantConfig"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/tenants/{tenant}/rules":{"get":{"tags":["Configuration"],"summary":"Get tenant cache rules","description":"Retrieve the rules for a specific tenant","operationId":"getTenantRules","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"responses":{"200":{"description":"Tenant cache rules","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantRules"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Configuration"],"summary":"Update tenant cache rules","description":"Update or create rules for a tenant","operationId":"updateTenantRules","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantRules"}}}},"responses":{"200":{"description":"Rules updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantRules"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/cachekeys/{cachekey}":{"get":{"tags":["Cache"],"summary":"Get cache metadata","description":"Retrieve metadata for a specific cache key","operationId":"getCacheMetadata","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"cachekey","in":"path","required":true,"description":"Cache key (hash)","schema":{"type":"string"},"example":"abc123def456"}],"responses":{"200":{"description":"Cache metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CacheMetadata"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Cache"],"summary":"Update cache metadata","description":"Update metadata for a specific cache key","operationId":"updateCacheMetadata","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"cachekey","in":"path","required":true,"description":"Cache key (hash)","schema":{"type":"string"},"example":"abc123def456"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CacheMetadata"}}}},"responses":{"200":{"description":"Cache metadata updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CacheMetadata"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/invalidation-markers":{"get":{"tags":["Markers"],"summary":"List invalidation markers","description":"Retrieve all invalidation markers for a tenant with optional filtering","operationId":"listMarkers","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"status","in":"query","required":false,"description":"Filter by marker status","schema":{"type":"string","enum":["active","expired"]}},{"name":"targetRuleId","in":"query","required":false,"description":"Filter by target rule ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of markers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerSummary"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Markers"],"summary":"Create invalidation marker","description":"Create a new invalidation marker (manual cache invalidation)","operationId":"createMarker","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInstance"}}}},"responses":{"201":{"description":"Marker created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInstance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/invalidation-markers/{markerId}":{"get":{"tags":["Markers"],"summary":"Get marker by ID","description":"Retrieve a specific invalidation marker","operationId":"getMarker","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"$ref":"#/components/parameters/MarkerIdParam"}],"responses":{"200":{"description":"Marker details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInstance"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Markers"],"summary":"Update marker","description":"Update an existing invalidation marker","operationId":"updateMarker","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"$ref":"#/components/parameters/MarkerIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInstance"}}}},"responses":{"200":{"description":"Marker updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInstance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Markers"],"summary":"Delete marker","description":"Delete a specific invalidation marker","operationId":"deleteMarker","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"$ref":"#/components/parameters/MarkerIdParam"}],"responses":{"200":{"description":"Marker deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/summaries/{year}":{"get":{"tags":["Summaries"],"summary":"Get yearly summary","description":"Retrieve yearly summary data for a tenant","operationId":"getYearlySummary","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025}],"responses":{"200":{"description":"Yearly summary data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/YearlySummary"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/summaries/{year}/rules":{"get":{"tags":["Summaries"],"summary":"Get rule effectiveness summary","description":"Retrieve cache rule effectiveness statistics for a year","operationId":"getRuleEffectiveness","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025}],"responses":{"200":{"description":"Rule effectiveness data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleEffectiveness"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/summaries/{year}/opportunities":{"get":{"tags":["Summaries"],"summary":"Get cache optimization opportunities","description":"Retrieve AI-identified opportunities for cache optimization","operationId":"getCacheOpportunities","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025}],"responses":{"200":{"description":"Cache optimization opportunities","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CacheOpportunities"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/summaries/{year}/{month}/{day}":{"get":{"tags":["Summaries"],"summary":"Get daily summary","description":"Retrieve daily summary data for a specific date","operationId":"getDailySummary","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025},{"name":"month","in":"path","required":true,"description":"Month (01-12)","schema":{"type":"string","pattern":"^(0[1-9]|1[0-2])$"},"example":"10"},{"name":"day","in":"path","required":true,"description":"Day of month (01-31)","schema":{"type":"string","pattern":"^(0[1-9]|[12][0-9]|3[01])$"},"example":"30"}],"responses":{"200":{"description":"Daily summary data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailySummary"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/users":{"get":{"tags":["Summaries"],"summary":"Get all users summary index","description":"Retrieve summary statistics for all users of a tenant","operationId":"getUsersIndex","parameters":[{"$ref":"#/components/parameters/TenantParam"}],"responses":{"200":{"description":"Users summary index","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersSummaryIndex"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/users/{userId}":{"get":{"tags":["Summaries"],"summary":"Get users summary index","description":"Retrieve summary statistics for all users of a tenant. The userId parameter is accepted but returns the full users index.","operationId":"getUserSummary","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"userId","in":"path","required":true,"description":"User ID (returns full users index)","schema":{"type":"string"},"example":"user@example.com"}],"responses":{"200":{"description":"Users summary index","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersSummaryIndex"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/users/{userId}/{year}":{"get":{"tags":["Summaries"],"summary":"Get user yearly summary","description":"Retrieve yearly summary for a specific user","operationId":"getUserYearlySummary","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"userId","in":"path","required":true,"description":"User ID","schema":{"type":"string"},"example":"user@example.com"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025}],"responses":{"200":{"description":"User yearly summary data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserYearlySummary"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/users/{userId}/{year}/{month}":{"get":{"tags":["Summaries"],"summary":"List user daily summaries for a month","description":"List all available daily summary files for a specific user in a given month, returned in HATEOAS format","operationId":"listUserMonthlySummaries","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"userId","in":"path","required":true,"description":"User ID","schema":{"type":"string"},"example":"user@example.com"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025},{"name":"month","in":"path","required":true,"description":"Month (01-12)","schema":{"type":"string","pattern":"^(0[1-9]|1[0-2])$"},"example":"02"}],"responses":{"200":{"description":"HATEOAS listing of daily summary files","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}}},"parent":{"type":"object","properties":{"href":{"type":"string"}}},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"href":{"type":"string"}}}}}},"tenantId":{"type":"string"},"userId":{"type":"string"},"year":{"type":"string"},"month":{"type":"string"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenant}/users/{userId}/{year}/{month}/{day}":{"get":{"tags":["Summaries"],"summary":"Get user daily summary","description":"Retrieve daily summary for a specific user","operationId":"getUserDailySummary","parameters":[{"$ref":"#/components/parameters/TenantParam"},{"name":"userId","in":"path","required":true,"description":"User ID","schema":{"type":"string"},"example":"user@example.com"},{"name":"year","in":"path","required":true,"description":"Year (e.g., 2025)","schema":{"type":"integer"},"example":2025},{"name":"month","in":"path","required":true,"description":"Month (01-12)","schema":{"type":"string","pattern":"^(0[1-9]|1[0-2])$"},"example":"10"},{"name":"day","in":"path","required":true,"description":"Day of month (01-31)","schema":{"type":"string","pattern":"^(0[1-9]|[12][0-9]|3[01])$"},"example":"30"}],"responses":{"200":{"description":"User daily summary data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDailySummary"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/airbrxai/rules-evaluator":{"post":{"tags":["AI"],"summary":"Generate cache rule recommendations","description":"Analyzes query patterns and generates AI-powered cache rule recommendations using Claude API. Requires `ANTHROPIC_API_KEY` environment variable to be set.","operationId":"evaluateRules","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RulesEvaluatorRequest"}}}},"responses":{"200":{"description":"AI-generated rule recommendations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RulesEvaluatorResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"description":"AI service error or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/logs/summarize":{"post":{"tags":["Logs"],"summary":"Trigger log summarization","description":"Invokes the log-summarizer Lambda (in Lambda environment) or local module (in Docker/local) to process and summarize log data across all tenants. Supports incremental processing, full reset, or lock clearing.","operationId":"triggerLogSummarize","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogSummarizeRequest"}}}},"responses":{"200":{"description":"Summarization completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogSummarizeResponse"}}}},"400":{"description":"Invalid mode specified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid mode: foo. Allowed: incremental, reset, unlock"}}}},"500":{"description":"Summarization failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/private-preview":{"get":{"tags":["Private Preview"],"summary":"List all private preview registrations","description":"Returns a HATEOAS listing of all private preview registrations. Requires PAT-derived access token (user JWTs are rejected).","operationId":"listPrivatePreviewRegistrations","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of registrations with HAL links","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string","example":"/private-preview"}}},"parent":{"type":"object","properties":{"href":{"type":"string","example":"/"}}},"items":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"jane.doe@acme.com"},"type":{"type":"string","enum":["registration"]},"href":{"type":"string","example":"/private-preview/jane.doe@acme.com"}}}}}}}}}}},"401":{"description":"Unauthorized - missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unauthorized: missing token"}}}},"403":{"description":"Forbidden - requires PAT-derived access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"userJwtRejected":{"value":{"error":"Forbidden: this endpoint requires a PAT-derived access token, not a user JWT"}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/private-preview/{email}":{"get":{"tags":["Private Preview"],"summary":"Get private preview registration","description":"Retrieve a single private preview registration by email. Requires PAT-derived access token (user JWTs are rejected).","operationId":"getPrivatePreviewRegistration","security":[{"BearerAuth":[]}],"parameters":[{"name":"email","in":"path","required":true,"description":"Email address of the registration","schema":{"type":"string","format":"email"},"example":"jane.doe@acme.com"}],"responses":{"200":{"description":"Registration details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivatePreviewRegistrationFull"}}}},"401":{"description":"Unauthorized - missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unauthorized: missing token"}}}},"403":{"description":"Forbidden - requires PAT-derived access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"userJwtRejected":{"value":{"error":"Forbidden: this endpoint requires a PAT-derived access token, not a user JWT"}}}}}},"404":{"description":"Registration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Registration not found"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/private-preview/register":{"post":{"tags":["Private Preview"],"summary":"Register for private preview","description":"Submit registration form for private preview access. Generates a 6-character verification code and stores registration to S3. Email verification is currently disabled (pending SES configuration), so the verification code is returned in the response for testing purposes.","operationId":"registerPrivatePreview","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivatePreviewRegistration"}}}},"responses":{"200":{"description":"Registration successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivatePreviewRegistrationResponse"}}}},"400":{"description":"Invalid request (missing fields or invalid email format)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missingFields":{"value":{"error":"Missing required fields: firstName, email"}},"invalidEmail":{"value":{"error":"Invalid email format"}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/private-preview/verify":{"post":{"tags":["Private Preview"],"summary":"Verify private preview registration","description":"Validate verification code and mark registration as verified. Verification codes expire after 24 hours.","operationId":"verifyPrivatePreview","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivatePreviewVerification"}}}},"responses":{"200":{"description":"Verification successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivatePreviewVerificationResponse"}}}},"400":{"description":"Missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Missing required fields: email and code"}}}},"401":{"description":"Invalid or expired verification code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalidCode":{"value":{"error":"Invalid verification code"}},"expiredCode":{"value":{"error":"Verification code has expired. Please register again."}}}}}},"404":{"description":"Registration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Registration not found"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/oauth/pats":{"post":{"tags":["OAuth"],"summary":"Create a Personal Access Token","description":"Create a new scoped PAT. Users can only grant permissions they have themselves.","operationId":"createPAT","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PATCreateRequest"}}}},"responses":{"201":{"description":"PAT created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PAT"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"description":"Forbidden - requested scopes exceed user permissions"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"get":{"tags":["OAuth"],"summary":"List user's Personal Access Tokens","description":"Returns all PATs belonging to the authenticated user with their status (valid/expired)","operationId":"listPATs","responses":{"200":{"description":"List of PATs","content":{"application/json":{"schema":{"type":"object","properties":{"pats":{"type":"array","items":{"$ref":"#/components/schemas/PATWithStatus"}}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/oauth/pats/{patName}":{"get":{"tags":["OAuth"],"summary":"Get PAT details","description":"Get full details of a PAT. Users can only view their own PATs unless they have full access.","operationId":"getPAT","parameters":[{"name":"patName","in":"path","required":true,"description":"PAT name (e.g., 'my_api_token')","schema":{"type":"string"}}],"responses":{"200":{"description":"PAT details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PATWithStatus"}}}},"403":{"description":"Forbidden - can only view your own PATs"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["OAuth"],"summary":"Revoke a Personal Access Token","description":"Delete/revoke a PAT. Users can only revoke their own PATs.","operationId":"revokePAT","parameters":[{"name":"patName","in":"path","required":true,"description":"PAT name (e.g., 'my_api_token')","schema":{"type":"string"}}],"responses":{"200":{"description":"PAT revoked successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"403":{"description":"Forbidden - can only revoke your own PATs"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/oauth/introspect":{"post":{"tags":["OAuth"],"summary":"Introspect a token (RFC 7662)","description":"Validate and get metadata for a JWT or PAT token. No authentication required.","operationId":"introspectToken","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"JWT or PAT token to introspect"}}}}}},"responses":{"200":{"description":"Token introspection result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntrospectionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/oauth/token":{"post":{"tags":["OAuth"],"summary":"Exchange PAT for access/refresh tokens or refresh an access token","description":"Token exchange endpoint supporting two grant types: 'pat_exchange' to exchange a PAT name for access and refresh tokens, and 'refresh_token' to get a new access token using a refresh token. The refresh_token grant supports both PAT-derived refresh tokens (sub='refresh', contains pat_hash) and user refresh tokens (sub='user_refresh', contains email) from the /auth/callback flow. PAT-derived access tokens include scopes for granular endpoint permissions; user access tokens use RBAC roles. No authentication required - credentials are passed in the request body.","operationId":"tokenExchange","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["grant_type","pat"],"properties":{"grant_type":{"type":"string","enum":["pat_exchange"],"description":"Grant type for PAT exchange"},"pat":{"type":"string","description":"The PAT name to exchange (e.g., 'my_api_token')"}}},{"type":"object","required":["grant_type","refresh_token"],"properties":{"grant_type":{"type":"string","enum":["refresh_token"],"description":"Grant type for token refresh"},"refresh_token":{"type":"string","description":"The refresh token (airbrx_refresh_...). Supports both PAT-derived refresh tokens (from pat_exchange) and user refresh tokens (from /auth/callback)"}}}]}}}},"responses":{"200":{"description":"Token exchange successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExchangeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/oauth/available-permissions":{"get":{"tags":["OAuth"],"summary":"Get available permissions for PAT creation","description":"Returns the permissions and tenants the authenticated user can grant to a PAT","operationId":"getAvailablePermissions","responses":{"200":{"description":"Available permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailablePermissions"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants":{"post":{"tags":["Permissions"],"summary":"Create a permission grant","description":"Grant access to a tenant to another user. Base admins can grant with delegation rights; delegators can only grant without delegation rights (level-2).","operationId":"createPermissionGrant","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantCreateRequest"}}}},"responses":{"201":{"description":"Grant created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"grant":{"$ref":"#/components/schemas/PermissionGrant"},"grantId":{"type":"string","example":"grant_abc123def456"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"description":"Forbidden - insufficient permissions to create grant"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants/by-me":{"get":{"tags":["Permissions"],"summary":"List grants made by me","description":"Returns all active permission grants created by the authenticated user","operationId":"listGrantsByMe","responses":{"200":{"description":"List of grants","content":{"application/json":{"schema":{"type":"object","properties":{"grants":{"type":"array","items":{"$ref":"#/components/schemas/PermissionGrant"}}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants/to-me":{"get":{"tags":["Permissions"],"summary":"List grants made to me","description":"Returns all active permission grants where the authenticated user is the grantee","operationId":"listGrantsToMe","responses":{"200":{"description":"List of grants","content":{"application/json":{"schema":{"type":"object","properties":{"grants":{"type":"array","items":{"$ref":"#/components/schemas/PermissionGrant"}}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants/tenant/{tenantId}":{"get":{"tags":["Permissions"],"summary":"List all grants for a tenant","description":"Returns all active grants for a specific tenant. Requires base admin access to the tenant.","operationId":"listGrantsByTenant","parameters":[{"name":"tenantId","in":"path","required":true,"description":"Tenant ID","schema":{"type":"string"},"example":"acme.app.airbrx.com"}],"responses":{"200":{"description":"List of grants","content":{"application/json":{"schema":{"type":"object","properties":{"grants":{"type":"array","items":{"$ref":"#/components/schemas/PermissionGrant"}}}}}}},"403":{"description":"Forbidden - only base administrators can list tenant grants"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants/{grantId}":{"get":{"tags":["Permissions"],"summary":"Get grant details","description":"Retrieve details of a specific permission grant. Accessible by grantor, grantee, root admin, or tenant admin.","operationId":"getPermissionGrant","parameters":[{"name":"grantId","in":"path","required":true,"description":"Grant ID","schema":{"type":"string"},"example":"grant_abc123def456"}],"responses":{"200":{"description":"Grant details","content":{"application/json":{"schema":{"type":"object","properties":{"grant":{"$ref":"#/components/schemas/PermissionGrant"}}}}}},"403":{"description":"Forbidden - not authorized to view this grant"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Permissions"],"summary":"Revoke a grant","description":"Revoke a permission grant. If the grant has delegation rights, all child grants are cascade-revoked. Accessible by grantor, root admin, or tenant admin.","operationId":"revokePermissionGrant","parameters":[{"name":"grantId","in":"path","required":true,"description":"Grant ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Grant revoked successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"revokedAt":{"type":"string","format":"date-time"},"cascadeRevoked":{"type":"array","items":{"type":"string"},"description":"IDs of child grants that were cascade-revoked"}}}}}},"403":{"description":"Forbidden - not authorized to revoke this grant"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"patch":{"tags":["Permissions"],"summary":"Update a grant","description":"Update grant properties (expiration, notes, delegation rights). Only root admin or tenant admin can change delegation rights on level-1 grants.","operationId":"updatePermissionGrant","parameters":[{"name":"grantId","in":"path","required":true,"description":"Grant ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantUpdateRequest"}}}},"responses":{"200":{"description":"Grant updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"grant":{"$ref":"#/components/schemas/PermissionGrant"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"description":"Forbidden - not authorized to update this grant"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants/{grantId}/invitees":{"get":{"tags":["Permissions"],"summary":"List invitees under a grant","description":"Returns all active child grants (level-2) created by the grantee of this grant using their delegation rights.","operationId":"listGrantInvitees","parameters":[{"name":"grantId","in":"path","required":true,"description":"Grant ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of invitee grants","content":{"application/json":{"schema":{"type":"object","properties":{"invitees":{"type":"array","items":{"$ref":"#/components/schemas/PermissionGrant"}}}}}}},"403":{"description":"Forbidden - not authorized to view invitees"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/permissions/grants/{grantId}/elevate":{"post":{"tags":["Permissions"],"summary":"Elevate a grant to level-1","description":"Promote a level-2 grant (created by a delegator) to level-1 (direct grant from base admin). Only base admins can elevate grants. The old grant is revoked with reason 'elevated'.","operationId":"elevatePermissionGrant","parameters":[{"name":"grantId","in":"path","required":true,"description":"Grant ID to elevate","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"canDelegate":{"type":"boolean","description":"Whether the elevated grant should have delegation rights","default":false}}}}}},"responses":{"200":{"description":"Grant elevated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"oldGrant":{"$ref":"#/components/schemas/PermissionGrant"},"newGrant":{"$ref":"#/components/schemas/PermissionGrant"}}}}}},"403":{"description":"Forbidden - only base administrators can elevate grants"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/accounts":{"get":{"tags":["Accounts"],"summary":"List all accounts","description":"Returns a list of all accounts with HATEOAS links","operationId":"listAccounts","responses":{"200":{"description":"List of accounts","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"$ref":"#/components/schemas/HATEOASLinks"},"accounts":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Accounts"],"summary":"Create a new account","description":"Creates a new account with a generated UUID","operationId":"createAccount","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreate"}}}},"responses":{"201":{"description":"Account created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Account name already exists"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/accounts/{accountId}":{"parameters":[{"name":"accountId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"get":{"tags":["Accounts"],"summary":"Get account by ID","description":"Returns a specific account by UUID","operationId":"getAccount","responses":{"200":{"description":"Account details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Accounts"],"summary":"Update account","description":"Updates an existing account. System accounts cannot be modified.","operationId":"updateAccount","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountUpdate"}}}},"responses":{"200":{"description":"Account updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"System accounts cannot be modified"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Account name already exists"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Accounts"],"summary":"Delete account (soft)","description":"Soft deletes an account by setting status to 'deleted'. System accounts cannot be deleted.","operationId":"deleteAccount","responses":{"200":{"description":"Account deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"System accounts cannot be deleted"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/accounts/{accountId}/tenants":{"parameters":[{"name":"accountId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"get":{"tags":["Accounts"],"summary":"List tenants for account","description":"Returns all tenants associated with this account (programmatic aggregation)","operationId":"listAccountTenants","responses":{"200":{"description":"List of tenants","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"$ref":"#/components/schemas/HATEOASLinks"},"tenants":{"type":"array","items":{"type":"object","properties":{"tenantId":{"type":"string","format":"uuid"},"name":{"type":"string"},"fqdns":{"type":"array","items":{"type":"object"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/accounts/{accountId}/domains":{"parameters":[{"name":"accountId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"get":{"tags":["Domains"],"summary":"List domains for account","description":"Returns all domains registered to this account","operationId":"listAccountDomains","responses":{"200":{"description":"List of domains","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"$ref":"#/components/schemas/HATEOASLinks"},"domains":{"type":"array","items":{"$ref":"#/components/schemas/DomainEntry"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Domains"],"summary":"Add domain to account","description":"Registers a new domain to this account. Domains must be globally unique.","operationId":"addAccountDomain","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Domain name (e.g., acme.com)","example":"acme.com"}}}}}},"responses":{"201":{"description":"Domain added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEntry"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Domain already registered to another account"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/accounts/{accountId}/domains/{domain}":{"parameters":[{"name":"accountId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"},{"name":"domain","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"get":{"tags":["Domains"],"summary":"Get domain details","description":"Returns details for a specific domain registered to this account","operationId":"getAccountDomain","responses":{"200":{"description":"Domain details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEntry"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Domains"],"summary":"Remove domain from account","description":"Unregisters a domain from this account","operationId":"removeAccountDomain","responses":{"200":{"description":"Domain removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/domains":{"get":{"tags":["Domains"],"summary":"List all domains","description":"Returns all domains across all accounts (programmatic aggregation)","operationId":"listAllDomains","responses":{"200":{"description":"List of all domains","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"$ref":"#/components/schemas/HATEOASLinks"},"domains":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/DomainEntry"},{"type":"object","properties":{"accountId":{"type":"string","format":"uuid"},"accountName":{"type":"string"}}}]}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/config/domains/{domain}":{"parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"get":{"tags":["Domains"],"summary":"Find domain owner","description":"Finds which account owns a specific domain (programmatic scan)","operationId":"getDomain","responses":{"200":{"description":"Domain details with owner","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/DomainEntry"},{"type":"object","properties":{"accountId":{"type":"string","format":"uuid"},"accountName":{"type":"string"},"_links":{"$ref":"#/components/schemas/HATEOASLinks"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenantId}/fqdns":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Tenant UUID"}],"get":{"tags":["FQDNs"],"summary":"List FQDNs for tenant","description":"Returns all FQDNs assigned to this tenant","operationId":"listTenantFqdns","responses":{"200":{"description":"List of FQDNs","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"$ref":"#/components/schemas/HATEOASLinks"},"fqdns":{"type":"array","items":{"$ref":"#/components/schemas/FqdnEntry"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["FQDNs"],"summary":"Add FQDN to tenant","description":"Assigns a new FQDN to this tenant. FQDNs must be globally unique.","operationId":"addTenantFqdn","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fqdn"],"properties":{"fqdn":{"type":"string","description":"Fully qualified domain name","example":"acme-prod.app.airbrx.com"},"isPrimary":{"type":"boolean","description":"Whether this is the primary FQDN for the tenant","default":false}}}}}},"responses":{"201":{"description":"FQDN added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FqdnEntry"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"FQDN already assigned to another tenant"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/tenants/{tenantId}/fqdns/{fqdn}":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Tenant UUID"},{"name":"fqdn","in":"path","required":true,"schema":{"type":"string"},"description":"Fully qualified domain name"}],"get":{"tags":["FQDNs"],"summary":"Get FQDN details","description":"Returns details for a specific FQDN assigned to this tenant","operationId":"getTenantFqdn","responses":{"200":{"description":"FQDN details","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/FqdnEntry"},{"type":"object","properties":{"tenantId":{"type":"string","format":"uuid"},"_links":{"$ref":"#/components/schemas/HATEOASLinks"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["FQDNs"],"summary":"Remove FQDN from tenant","description":"Unassigns an FQDN from this tenant","operationId":"removeTenantFqdn","responses":{"200":{"description":"FQDN removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/fqdns":{"get":{"tags":["FQDNs"],"summary":"List all FQDNs","description":"Returns all FQDNs across all tenants (programmatic aggregation)","operationId":"listAllFqdns","responses":{"200":{"description":"List of all FQDNs","content":{"application/json":{"schema":{"type":"object","properties":{"_links":{"$ref":"#/components/schemas/HATEOASLinks"},"fqdns":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/FqdnEntry"},{"type":"object","properties":{"tenantId":{"type":"string","format":"uuid"},"tenantName":{"type":"string"}}}]}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["FQDNs"],"summary":"Create FQDN index entry","description":"Create a new FQDN-to-tenant mapping. Returns 409 if the FQDN is already in use or has been tombstoned (permanently retired).","operationId":"createFqdn","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fqdn","tenantId"],"properties":{"fqdn":{"type":"string","description":"The fully qualified domain name to assign","example":"analytics.acme.com"},"tenantId":{"type":"string","format":"uuid","description":"Tenant UUID to assign this FQDN to"},"isPrimary":{"type":"boolean","description":"Whether this is the tenant's primary FQDN","default":false},"assignedBy":{"type":"string","description":"Email or identifier of the person making the assignment"}}}}}},"responses":{"201":{"description":"FQDN index entry created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FqdnEntry"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/fqdns/{fqdn}":{"parameters":[{"name":"fqdn","in":"path","required":true,"schema":{"type":"string"},"description":"Fully qualified domain name"}],"get":{"tags":["FQDNs"],"summary":"Find FQDN owner","description":"Finds which tenant owns a specific FQDN (programmatic scan)","operationId":"getFqdn","responses":{"200":{"description":"FQDN details with owner","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/FqdnEntry"},{"type":"object","properties":{"tenantId":{"type":"string","format":"uuid"},"tenantName":{"type":"string"},"_links":{"$ref":"#/components/schemas/HATEOASLinks"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["FQDNs"],"summary":"Update FQDN metadata","description":"Update metadata on an active FQDN index entry (e.g., isPrimary). Cannot change tenantId (returns 409).","operationId":"updateFqdn","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"isPrimary":{"type":"boolean"},"assignedBy":{"type":"string"},"note":{"type":"string","description":"Audit note for this update"}}}}}},"responses":{"200":{"description":"FQDN updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FqdnEntry"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["FQDNs"],"summary":"Tombstone an FQDN","description":"Permanently retire an FQDN. Creates a tombstone record and removes the active index. The FQDN can never be reassigned.","operationId":"deleteFqdn","responses":{"200":{"description":"FQDN tombstoned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"FQDN 'analytics.acme.com' has been permanently retired"},"tombstone":{"$ref":"#/components/schemas/FqdnTombstone"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Airbrx JWT token. Can be a user JWT (from OAuth login) or a PAT-derived access token (from pat_exchange). Include in Authorization header as: `Bearer <token>`"},"OAuth2":{"type":"oauth2","description":"Google OAuth 2.0 authentication","flows":{"authorizationCode":{"authorizationUrl":"https://accounts.google.com/o/oauth2/v2/auth","tokenUrl":"https://oauth2.googleapis.com/token","scopes":{"openid":"OpenID Connect","profile":"User profile information","email":"User email address"}}}}},"parameters":{"EmailParam":{"name":"email","in":"path","required":true,"description":"User email address","schema":{"type":"string","format":"email"},"example":"user@example.com"},"TenantParam":{"name":"tenant","in":"path","required":true,"description":"Tenant ID (domain)","schema":{"type":"string"},"example":"demo.app.airbrx.com"},"MarkerIdParam":{"name":"markerId","in":"path","required":true,"description":"Marker identifier","schema":{"type":"string"},"example":"marker-1706140800000-abc123"}},"schemas":{"User":{"type":"object","description":"User profile with OAuth data and Airbrx-specific fields","properties":{"email":{"type":"string","format":"email","description":"User email (unique identifier)"},"name":{"type":"string","description":"Full name from OAuth provider"},"picture":{"type":"string","description":"Base64-encoded data URL of profile picture"},"roles":{"$ref":"#/components/schemas/Roles"},"tenants":{"type":"array","items":{"type":"string"},"description":"List of tenant IDs the user has access to","default":["demo.app.airbrx.com"]},"lastLogin":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of last login"},"displayName":{"type":"string","description":"Display name for the user"},"authProvider":{"type":"string","description":"Authentication provider (e.g. descope, google)"},"delegatedAccess":{"type":"array","description":"Delegated access grants for this user","items":{"type":"object","properties":{"tenantId":{"type":"string","description":"Tenant ID the grant applies to"},"role":{"type":"string","description":"Role granted for the tenant"},"grantId":{"type":"string","description":"ID of the permission grant"},"grantedBy":{"type":"string","description":"Email of the user who granted access"},"canDelegate":{"type":"boolean","description":"Whether the grantee can further delegate"}}},"default":[]},"scopes":{"$ref":"#/components/schemas/PATScopes","description":"Explicit per-method endpoint scopes. When set, these override role-based scope expansion in the JWT."}},"required":["email"]},"Roles":{"type":"object","description":"User role configuration","properties":{"primary":{"type":"string","description":"Primary role name (used for RBAC)","enum":["admin","user","guest","viewer","operator"],"default":"guest"}},"additionalProperties":{"type":"string"},"example":{"primary":"admin","secondary":"analyst"}},"TenantConfig":{"type":"object","description":"Tenant configuration (v2.0 schema with UUID-based tenantId and account support)","required":["tenantId","tenantName","accountId","primaryFqdn","dataAdapter","status","version"],"properties":{"tenantId":{"type":"string","format":"uuid","description":"Unique tenant identifier (UUID v4)"},"tenantName":{"type":"string","description":"Display name (unique within account)","minLength":1,"maxLength":255},"accountId":{"type":"string","format":"uuid","description":"Parent account reference (UUID v4)"},"primaryFqdn":{"type":"string","description":"Main FQDN for request routing (globally unique)"},"aliasFqdns":{"type":"array","items":{"type":"string"},"description":"Additional FQDNs that route to this tenant"},"dataAdapter":{"type":"object","description":"Data warehouse connection configuration","required":["type","server_hostname"],"properties":{"type":{"type":"string","enum":["databricks","snowflake"],"description":"Warehouse backend type"},"server_hostname":{"type":"string","description":"Backend warehouse hostname"},"http_path":{"type":"string","description":"Databricks SQL endpoint path"},"cloudFilesBaseUrl":{"type":"string","nullable":true,"description":"CloudFile proxy base URL"}}},"storage":{"type":"object","description":"Operational data storage (cache, sessions, operations, tokens, users). Inherits from account defaults if omitted.","properties":{"type":{"type":"string","enum":["s3express","s3","filesystem"],"description":"Storage backend type","default":"s3express"},"bucket":{"type":"string","description":"S3 bucket name"},"region":{"type":"string","description":"AWS region","default":"us-east-1"},"endpoint":{"type":"string","description":"Custom S3 endpoint (for LocalStack/MinIO)"}}},"logStorage":{"type":"object","description":"Log storage configuration. Inherits from account defaults if omitted.","properties":{"type":{"type":"string","enum":["s3","s3express","filesystem"],"description":"Storage backend type","default":"s3"},"bucket":{"type":"string","description":"S3 bucket name"},"region":{"type":"string","description":"AWS region","default":"us-east-1"},"endpoint":{"type":"string","description":"Custom S3 endpoint (for LocalStack/MinIO)"}}},"status":{"type":"string","enum":["active","suspended","deleted"],"description":"Tenant status"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp (UTC)"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp (UTC)"},"version":{"type":"string","description":"Schema version","enum":["2.0"],"default":"2.0"}},"example":{"tenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","tenantName":"Acme Production","accountId":"550e8400-e29b-41d4-a716-446655440000","primaryFqdn":"analytics.acme.com","aliasFqdns":["acme-prod.app.airbrx.com"],"dataAdapter":{"type":"databricks","server_hostname":"acme-prod.cloud.databricks.com","http_path":"/sql/1.0/warehouses/abc123"},"storage":{"type":"s3express","bucket":"airbrx-cache-prod--use1-az4--x-s3"},"logStorage":{"type":"s3","bucket":"airbrx-logs-prod"},"status":"active","createdAt":"2026-01-15T10:30:00.000Z","updatedAt":"2026-01-20T14:22:00.000Z","version":"2.0"}},"TenantRules":{"type":"object","description":"Cache rules configuration (v2.0 schema with UUID-based tenantId)","required":["version","tenantId","rules"],"properties":{"version":{"type":"string","description":"Schema version","enum":["2.0"],"default":"2.0"},"tenantId":{"type":"string","format":"uuid","description":"Tenant identifier (UUID v4)"},"defaults":{"type":"object","description":"Default values for rules that don't specify them","properties":{"cacheKeyElements":{"type":"array","items":{"type":"string","enum":["userId","userRole","standardizedSql","statement","catalog","schema","tables","columns","warehouse","tenantId"]},"description":"Default cache key elements"},"ttlSeconds":{"type":"integer","description":"Default TTL in seconds"},"version":{"type":"integer","description":"Default cache key version"}}},"rules":{"type":"array","items":{"type":"object","required":["id","priority","conditions","actions"],"properties":{"id":{"type":"string","description":"Unique rule identifier (kebab-case recommended)"},"name":{"type":"string","description":"Human-readable rule name"},"description":{"type":"string","description":"Detailed explanation of rule purpose"},"priority":{"type":"integer","description":"Evaluation order (1 = highest priority, lower wins)","minimum":1,"maximum":100},"enabled":{"type":"boolean","description":"Whether rule is active","default":true},"mode":{"type":"string","description":"Condition matching mode","enum":["all","either"],"default":"all"},"respectSqlHints":{"type":"boolean","description":"Honor SQL cache hints (__AIRBRX_CACHE__, __AIRBRX_NOCACHE__)","default":true},"conditions":{"type":"object","description":"Conditions that must match for rule to apply","properties":{"statementType":{"type":"object","description":"SQL statement type (SELECT, INSERT, UPDATE, DELETE, etc.). Operators: equals, in, matches"},"tables":{"type":"object","description":"Tables in query. Operators: includes, includesAny, includesAll, notIncludes"},"schema":{"type":"object","description":"Schema name. Operators: equals, in, matches"},"catalog":{"type":"object","description":"Catalog name. Operators: equals, matches"},"columns":{"type":"object","description":"Columns in query. Operators: includes, includesAll"},"standardizedSql":{"type":"object","description":"Normalized SQL. Operators: matches, contains, startsWith"},"statement":{"type":"object","description":"Raw SQL text. Operators: matches, contains, startsWith"},"hasParameters":{"type":"boolean","description":"Whether query has parameter placeholders"},"parameters":{"type":"object","description":"Parameter value conditions. Operators: equals, notEquals, in, notIn, matches, exists, greaterThan, lessThan"},"userId":{"type":"object","description":"User identifier. Operators: equals, in, matches"},"userRole":{"type":"object","description":"User role. Operators: equals, in"},"httpHeaders":{"type":"object","description":"HTTP headers. Operators: equals, in, matches, contains, exists"}},"additionalProperties":true},"actions":{"type":"object","description":"Actions when rule matches","properties":{"cacheKeyElements":{"type":"array","items":{"type":"string","enum":["userId","userRole","standardizedSql","statement","catalog","schema","tables","columns","warehouse","tenantId"]},"description":"Elements to include in cache key hash"},"cache":{"type":"object","description":"Cache configuration","properties":{"ttlSeconds":{"type":"integer","description":"Time-to-live in seconds (0 = no cache)","minimum":0}}},"version":{"type":"integer","description":"Cache key version for invalidation","minimum":1},"deleteCache":{"type":"boolean","description":"Delete existing cache entries for matching queries"}}},"invalidateRules":{"type":"array","items":{"type":"string"},"description":"Rule IDs whose caches to invalidate when this DML rule matches","default":[]},"requireInvalidation":{"type":"boolean","description":"If true, DML fails when invalidation fails (for security-critical operations)","default":false},"tags":{"type":"array","items":{"type":"string"},"description":"Categorization tags for rule organization","default":[]}}}}},"example":{"version":"2.0","tenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","defaults":{"cacheKeyElements":["userId","warehouse","standardizedSql"],"ttlSeconds":3600,"version":1},"rules":[{"id":"select-customer-data","name":"Cache Customer Queries","priority":10,"conditions":{"statementType":{"equals":"SELECT"},"tables":{"includes":"CUSTOMER"}},"actions":{"cache":{"ttlSeconds":3600}}},{"id":"insert-orders","name":"Invalidate on Order Insert","priority":5,"conditions":{"statementType":{"equals":"INSERT"},"tables":{"includes":"ORDERS"}},"actions":{"cache":{"ttlSeconds":0}},"invalidateRules":["select-orders","orders-summary"]}]}},"CacheMetadata":{"type":"object","description":"Cache entry metadata","properties":{"version":{"type":"integer","description":"Metadata format version"},"cacheKey":{"type":"string","description":"Cache key hash"},"statement":{"type":"string","description":"Original SQL statement"},"user":{"type":"string","description":"Username"},"warehouse":{"type":"string","description":"Warehouse identifier"},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when cache entry was created"},"expiresAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when cache expires"},"lastAccessed":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of last access"},"hitCount":{"type":"integer","description":"Number of cache hits"},"size":{"type":"integer","description":"Cache entry size in bytes"}},"additionalProperties":true},"MarkerInstance":{"type":"object","description":"Cache invalidation marker instance","required":["markerId","targetRuleId"],"properties":{"markerId":{"type":"string","description":"Unique marker identifier"},"sourceRuleId":{"type":"string","description":"DML rule that triggered this marker (null if manually created)"},"targetRuleId":{"type":"string","description":"Cache rule being invalidated"},"tenantId":{"type":"string","description":"Tenant identifier"},"status":{"type":"string","enum":["active","expired"],"description":"Marker status","default":"active"},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when marker was created"},"expiresAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when marker expires"},"metadata":{"type":"object","description":"Additional context about the marker","properties":{"triggerStatement":{"type":"string","description":"DML statement that triggered the marker"},"userId":{"type":"string","description":"User who triggered the invalidation"},"reason":{"type":"string","description":"Reason for manual marker creation/update"}},"additionalProperties":true}},"additionalProperties":true},"MarkerSummary":{"type":"object","description":"Summary of markers for a tenant","properties":{"tenantId":{"type":"string","description":"Tenant identifier"},"totalCount":{"type":"integer","description":"Total number of markers"},"activeCount":{"type":"integer","description":"Number of active markers"},"expiredCount":{"type":"integer","description":"Number of expired markers"},"markers":{"type":"array","items":{"$ref":"#/components/schemas/MarkerInstance"},"description":"List of markers"}}},"YearlySummary":{"type":"object","description":"Yearly summary statistics for a tenant","properties":{"tenantSummaryReportID":{"type":"string","format":"uuid","description":"Unique identifier for this summary report"},"tenantId":{"type":"string","description":"Tenant identifier"},"reportyears":{"type":"array","items":{"type":"string"},"description":"Years included in this report"},"totalUsers":{"type":"integer","description":"Total number of unique users"},"totalQueries":{"type":"integer","description":"Total number of queries"},"totalCacheHits":{"type":"integer","description":"Total number of cache hits"},"totalHits":{"type":"integer","description":"Total number of requests"},"cacheSize":{"type":"integer","description":"Total cache size in bytes"},"cacheElements":{"type":"integer","description":"Total number of cached elements"},"last365daysQueryActivity":{"type":"array","description":"Daily activity for up to 365 days","items":{"$ref":"#/components/schemas/DailyActivity"}}},"additionalProperties":true},"DailyActivity":{"type":"object","description":"Daily activity statistics within a yearly summary","properties":{"date":{"type":"string","description":"Date in YYYY/MM/DD format","example":"2025/10/30"},"queries":{"type":"integer","description":"Number of unique queries"},"users":{"type":"integer","description":"Number of unique users"},"cacheHits":{"type":"integer","description":"Number of cache hits"},"totalHits":{"type":"integer","description":"Total number of requests"},"dataTransfer":{"type":"integer","description":"Data transfer in bytes"},"cacheSize":{"type":"integer","description":"Cache size in bytes"},"cacheElements":{"type":"integer","description":"Number of cached elements"},"peakHour":{"type":"integer","minimum":0,"maximum":23,"description":"Hour with highest query volume (0-23, UTC)"},"cacheHitRate":{"type":"number","minimum":0,"maximum":1,"description":"Cache hit rate for the day (0.0 to 1.0)"}}},"DailySummary":{"type":"object","description":"Daily summary statistics with query details","properties":{"queries":{"type":"array","description":"Array of query statistics","items":{"$ref":"#/components/schemas/QuerySummary"}},"uniqueUsers":{"type":"integer","description":"Number of unique users"},"cacheHits":{"type":"integer","description":"Total cache hits"},"totalHits":{"type":"integer","description":"Total number of requests"},"averageResponseTime":{"type":"integer","description":"Average response time in milliseconds"},"minResponseTime":{"type":"integer","description":"Minimum response time in milliseconds"},"maxResponseTime":{"type":"integer","description":"Maximum response time in milliseconds"},"hourlyActivity":{"type":"array","description":"24 elements representing hourly activity breakdown, index 0 = midnight UTC","items":{"type":"object","properties":{"hour":{"type":"integer","minimum":0,"maximum":23,"description":"Hour of day (0-23, UTC)"},"queries":{"type":"integer","description":"Number of unique queries in this hour"},"totalHits":{"type":"integer","description":"Total number of requests in this hour"},"cacheHits":{"type":"integer","description":"Number of cache hits in this hour"},"cacheMisses":{"type":"integer","description":"Number of cache misses in this hour"},"users":{"type":"integer","description":"Number of unique users in this hour"}}}}},"additionalProperties":true},"QuerySummary":{"type":"object","description":"Statistics for a specific query","properties":{"queryHash":{"type":"string","description":"Hash of the SQL statement"},"statement":{"type":"string","description":"SQL query text"},"cacheKey":{"type":"string","description":"Cache key or filename"},"cacheHits":{"type":"integer","description":"Number of cache hits for this query"},"responseCode":{"type":"integer","description":"HTTP response code"},"averageResponseTime":{"type":"integer","description":"Average response time in milliseconds"},"responseSize":{"type":"integer","description":"Response size in bytes"},"count":{"type":"integer","description":"Number of times this query was executed"},"firstRequestTime":{"type":"string","description":"Timestamp of first request"},"lastRequestTime":{"type":"string","description":"Timestamp of last request"},"matchedRule":{"type":["string","null"],"description":"Rule ID that matched this query, null if no rule matched"},"cacheStatus":{"type":"string","enum":["HIT","MISS","BYPASS","PASSTHROUGH"],"description":"Most recent cache status for this query"},"cacheMisses":{"type":"integer","description":"Number of cache misses for this query"},"warehouseExecutionTimeMs":{"type":["integer","null"],"description":"Total warehouse execution time in milliseconds, null if query was never sent to warehouse"}}},"RuleEffectiveness":{"type":"object","description":"Cache rule effectiveness statistics for a year","properties":{"tenantId":{"type":"string","description":"Tenant identifier"},"year":{"type":"integer","description":"Year of the summary"},"generatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when this summary was generated"},"totalQueries":{"type":"integer","description":"Total number of queries"},"uncachedQueries":{"type":"integer","description":"Number of queries that were not cached"},"bypassedQueries":{"type":"integer","description":"Number of queries that bypassed cache"},"rules":{"type":"array","description":"Per-rule effectiveness statistics","items":{"type":"object","properties":{"ruleId":{"type":"string","description":"Rule identifier"},"ruleName":{"type":"string","description":"Human-readable rule name"},"queriesMatched":{"type":"integer","description":"Number of unique queries that matched this rule"},"totalExecutions":{"type":"integer","description":"Total executions of queries matching this rule"},"cacheHits":{"type":"integer","description":"Number of cache hits for this rule"},"cacheMisses":{"type":"integer","description":"Number of cache misses for this rule"},"hitRate":{"type":"number","minimum":0,"maximum":1,"description":"Cache hit rate (0.0 to 1.0)"},"warehouseTimeSavedMs":{"type":"integer","description":"Estimated warehouse time saved in milliseconds"}}}}}},"CacheOpportunities":{"type":"object","description":"AI-identified opportunities for cache optimization","properties":{"tenantId":{"type":"string","description":"Tenant identifier"},"year":{"type":"integer","description":"Year of the analysis"},"generatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when this analysis was generated"},"repeatMisses":{"type":"array","description":"Queries with repeated cache misses that could benefit from caching","items":{"$ref":"#/components/schemas/CacheOpportunity"}},"highCostUncached":{"type":"array","description":"High-cost queries that are not cached","items":{"$ref":"#/components/schemas/CacheOpportunity"}},"lowHitRate":{"type":"array","description":"Cached queries with low hit rates","items":{"$ref":"#/components/schemas/CacheOpportunity"}}}},"CacheOpportunity":{"type":"object","description":"A single cache optimization opportunity","properties":{"queryHash":{"type":"string","description":"Hash of the SQL statement"},"statement":{"type":"string","description":"SQL query text"},"executionCount":{"type":"integer","description":"Number of times this query was executed"},"cacheMisses":{"type":"integer","description":"Number of cache misses"},"totalWarehouseTimeMs":{"type":"integer","description":"Total warehouse execution time in milliseconds"},"matchedRule":{"type":["string","null"],"description":"Rule ID that matched, null if no rule matched"},"suggestedAction":{"type":"string","enum":["CREATE_RULE","EXTEND_TTL","REDUCE_TTL","INVESTIGATE"],"description":"Recommended action to improve caching"}}},"UsersSummaryIndex":{"type":"object","description":"Summary index of all users for a tenant","properties":{"tenantId":{"type":"string","description":"Tenant identifier"},"generatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when this summary was generated"},"totalUsers":{"type":"integer","description":"Total number of users"},"users":{"type":"array","description":"Array of user summary statistics","items":{"$ref":"#/components/schemas/UserSummaryItem"}}}},"UserSummaryItem":{"type":"object","description":"Summary statistics for a single user","properties":{"userId":{"type":"string","description":"User identifier"},"uniqueQueries":{"type":"integer","description":"Number of unique queries by this user"},"cacheHits":{"type":"integer","description":"Total cache hits for this user"},"totalHits":{"type":"integer","description":"Total requests by this user"},"averageResponseTime":{"type":"integer","description":"Average response time in milliseconds"},"minResponseTime":{"type":"integer","description":"Minimum response time in milliseconds"},"maxResponseTime":{"type":"integer","description":"Maximum response time in milliseconds"}}},"UserSummary":{"type":"object","description":"Aggregate summary for a specific user across all time periods","properties":{"userId":{"type":"string","description":"User identifier"},"totalQueries":{"type":"integer","description":"Total number of queries"},"totalCacheHits":{"type":"integer","description":"Total cache hits"},"totalHits":{"type":"integer","description":"Total requests"},"averageResponseTime":{"type":"integer","description":"Average response time in milliseconds"},"minResponseTime":{"type":"integer","description":"Minimum response time in milliseconds"},"maxResponseTime":{"type":"integer","description":"Maximum response time in milliseconds"}},"additionalProperties":true},"UserYearlySummary":{"type":"object","description":"Yearly summary statistics for a specific user","properties":{"userSummaryReportID":{"type":"string","format":"uuid","description":"Unique identifier for this summary report"},"tenantId":{"type":"string","description":"Tenant identifier"},"userId":{"type":"string","description":"User identifier"},"reportyears":{"type":"array","items":{"type":"string"},"description":"Years included in this report"},"totalQueries":{"type":"integer","description":"Total number of queries"},"totalCacheHits":{"type":"integer","description":"Total cache hits"},"totalHits":{"type":"integer","description":"Total requests"},"last365daysQueryActivity":{"type":"array","description":"Daily activity for up to 365 days","items":{"$ref":"#/components/schemas/UserDailyActivity"}}},"additionalProperties":true},"UserDailyActivity":{"type":"object","description":"Daily activity statistics for a user within a yearly summary","properties":{"date":{"type":"string","description":"Date in YYYY/MM/DD format","example":"2025/10/30"},"queries":{"type":"integer","description":"Number of queries"},"cacheHits":{"type":"integer","description":"Number of cache hits"},"totalHits":{"type":"integer","description":"Total requests"}}},"UserDailySummary":{"type":"object","description":"Daily summary statistics for a specific user with query details","properties":{"userId":{"type":"string","description":"User identifier"},"uniqueQueries":{"type":"integer","description":"Number of unique queries"},"cacheHits":{"type":"integer","description":"Total cache hits"},"totalHits":{"type":"integer","description":"Total requests"},"averageResponseTime":{"type":"integer","description":"Average response time in milliseconds"},"minResponseTime":{"type":"integer","description":"Minimum response time in milliseconds"},"maxResponseTime":{"type":"integer","description":"Maximum response time in milliseconds"},"queries":{"type":"array","description":"Array of query statistics","items":{"$ref":"#/components/schemas/QuerySummary"}}},"additionalProperties":true},"RulesEvaluatorRequest":{"type":"object","description":"Request body for AI rules evaluator","required":["queries"],"properties":{"queries":{"type":"array","description":"Array of query objects to analyze","items":{"type":"object","properties":{"statement":{"type":"string","description":"SQL query text"},"user":{"type":"string","description":"Username"},"frequency":{"type":"integer","description":"Number of times this query was executed"},"avgDuration":{"type":"number","description":"Average execution time in milliseconds"}},"required":["statement"]}}},"example":{"queries":[{"statement":"SELECT * FROM sales WHERE date > '2025-01-01'","user":"analyst@example.com","frequency":150,"avgDuration":2300},{"statement":"SELECT COUNT(*) FROM users","user":"admin@example.com","frequency":500,"avgDuration":120}]}},"RulesEvaluatorResponse":{"type":"object","description":"AI-generated rule recommendations","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"priority":{"type":"integer","description":"Evaluation order (lower = higher priority)"},"enabled":{"type":"boolean","description":"Whether rule is active"},"mode":{"type":"string","enum":["all","either"],"description":"Condition matching mode"},"conditions":{"type":"object","description":"Conditions that must match for rule to apply","additionalProperties":true},"actions":{"type":"object","description":"Actions to take when rule matches","properties":{"cacheKeyElements":{"type":"array","items":{"type":"string"}},"cache":{"type":"object","properties":{"ttlSeconds":{"type":"integer"}}},"version":{"type":"integer"}}},"rationale":{"type":"string","description":"AI explanation for the recommendation"}}}},"error":{"type":"string","description":"Error message if JSON parsing failed"},"rawResponse":{"type":"string","description":"Raw AI response if JSON parsing failed"}},"example":{"rules":[{"id":"rule_001","name":"Cache frequent sales queries","description":"Cache sales dashboard queries with high frequency","enabled":true,"priority":10,"mode":"all","conditions":{"tables":{"includes":"SALES"},"statement":{"contains":"FROM sales"},"userId":{"matches":".*@example\\.com"}},"actions":{"cacheKeyElements":["userId","standardizedSql"],"cache":{"ttlSeconds":3600},"version":1},"rationale":"This query runs 150 times per day with consistent results. Caching for 1 hour would reduce warehouse load significantly."}]}},"PrivatePreviewRegistration":{"type":"object","description":"Private preview registration request","required":["firstName","lastName","companyName","email","phone"],"properties":{"firstName":{"type":"string","description":"Registrant's first name","minLength":1},"lastName":{"type":"string","description":"Registrant's last name","minLength":1},"companyName":{"type":"string","description":"Company or organization name","minLength":1},"email":{"type":"string","format":"email","description":"Valid email address"},"phone":{"type":"string","description":"Contact phone number","minLength":1}},"example":{"firstName":"Jane","lastName":"Doe","companyName":"Acme Corp","email":"jane.doe@acme.com","phone":"+1-555-0100"}},"PrivatePreviewRegistrationResponse":{"type":"object","description":"Private preview registration response","properties":{"success":{"type":"boolean","description":"Registration success status"},"message":{"type":"string","description":"Success message"},"verificationCode":{"type":"string","description":"6-character verification code (returned for testing only - will be removed when email is enabled)","pattern":"^[A-Z2-9]{6}$"}},"example":{"success":true,"message":"Registration successful for jane.doe@acme.com","verificationCode":"ABC3D5"}},"PrivatePreviewVerification":{"type":"object","description":"Private preview verification request","required":["email","code"],"properties":{"email":{"type":"string","format":"email","description":"Email address used during registration"},"code":{"type":"string","description":"6-character verification code","pattern":"^[A-Z2-9]{6}$"}},"example":{"email":"jane.doe@acme.com","code":"ABC3D5"}},"PrivatePreviewRegistrationFull":{"type":"object","description":"Full private preview registration object (admin view)","properties":{"firstName":{"type":"string","description":"Registrant's first name"},"lastName":{"type":"string","description":"Registrant's last name"},"companyName":{"type":"string","description":"Company or organization name"},"email":{"type":"string","format":"email","description":"Email address"},"phone":{"type":"string","description":"Contact phone number"},"verificationCode":{"type":"string","description":"6-character verification code","pattern":"^[A-Z2-9]{6}$"},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when registration was created"},"verified":{"type":"boolean","description":"Whether the registration has been verified"},"verifiedAt":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp when registration was verified (null if not verified)"}},"example":{"firstName":"Jane","lastName":"Doe","companyName":"Acme Corp","email":"jane.doe@acme.com","phone":"+1-555-0100","verificationCode":"ABC3D5","createdAt":"2025-01-15T10:30:00.000Z","verified":true,"verifiedAt":"2025-01-15T10:35:00.000Z"}},"PrivatePreviewVerificationResponse":{"type":"object","description":"Private preview verification response","properties":{"success":{"type":"boolean","description":"Verification success status"},"verified":{"type":"boolean","description":"Whether the registration is now verified"},"registration":{"type":"object","description":"Registration data (excluding verification code)","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"companyName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"}}}},"example":{"success":true,"verified":true,"registration":{"firstName":"Jane","lastName":"Doe","companyName":"Acme Corp","email":"jane.doe@acme.com","phone":"+1-555-0100"}}},"LogSummarizeRequest":{"type":"object","description":"Log summarization request","properties":{"mode":{"type":"string","description":"Summarization mode","enum":["incremental","reset","unlock"],"default":"incremental"}},"example":{"mode":"incremental"}},"LogSummarizeResponse":{"type":"object","description":"Log summarization response","properties":{"success":{"type":"boolean","description":"Whether summarization completed successfully"},"tenantsProcessed":{"type":"integer","description":"Number of tenants processed"},"duration":{"type":"integer","description":"Execution time in milliseconds"},"mode":{"type":"string","description":"Mode that was used","enum":["incremental","reset","unlock"]},"error":{"type":"string","description":"Error message if success is false"}},"example":{"success":true,"tenantsProcessed":5,"duration":2340,"mode":"incremental"}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error message"}},"required":["error"]},"PATScopes":{"type":"object","description":"PAT permission scopes by HTTP method","properties":{"GET":{"type":"array","items":{"type":"string"},"description":"Paths allowed for GET requests"},"POST":{"type":"array","items":{"type":"string"},"description":"Paths allowed for POST requests"},"PUT":{"type":"array","items":{"type":"string"},"description":"Paths allowed for PUT requests"},"DELETE":{"type":"array","items":{"type":"string"},"description":"Paths allowed for DELETE requests"}},"example":{"GET":["/tenants/{tenantId}/**"],"POST":[],"PUT":[],"DELETE":[]}},"PATCreateRequest":{"type":"object","description":"Request body for creating a PAT","required":["name","scopes"],"properties":{"name":{"type":"string","description":"Human-readable name for the PAT"},"expiresAt":{"type":"string","format":"date-time","description":"Optional expiration date (ISO 8601)"},"scopes":{"$ref":"#/components/schemas/PATScopes"},"tenants":{"type":"array","items":{"type":"string"},"description":"Optional explicit list of tenants (auto-extracted from scope paths if not provided)"}},"example":{"name":"CI/CD Pipeline Token","expiresAt":"2026-12-02T10:00:00Z","scopes":{"GET":["/tenants/acme.app.airbrx.com/**"],"POST":[],"PUT":[],"DELETE":[]}}},"PAT":{"type":"object","description":"Personal Access Token. The 'name' field is the identifier used for pat_exchange.","required":["name","issuedBy","createdAt","scopes","tenants","accounts"],"properties":{"name":{"type":"string","description":"PAT identifier. Use this value in pat_exchange to get access tokens."},"issuedBy":{"type":"string","format":"email","description":"Email of user who created the PAT"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"Expiration timestamp. Null means never expires."},"lastUsedAt":{"type":"string","format":"date-time","nullable":true,"description":"Last usage timestamp. Null if never used."},"scopes":{"$ref":"#/components/schemas/PATScopes"},"tenants":{"type":"array","items":{"type":"string"},"description":"Tenant IDs this PAT can access. Use [\"*\"] for all tenants."},"accounts":{"type":"array","items":{"type":"string"},"description":"Account IDs this PAT can access. Use [\"*\"] for all accounts. When omitted on create, derived from tenants."}}},"PATWithStatus":{"allOf":[{"$ref":"#/components/schemas/PAT"},{"type":"object","properties":{"status":{"type":"string","enum":["valid","expired"],"description":"Computed status based on expiration. To revoke a PAT, delete it."}}}]},"IntrospectionResponse":{"type":"object","description":"Token introspection response (RFC 7662)","properties":{"active":{"type":"boolean","description":"Whether the token is valid and not expired"},"token_type":{"type":"string","description":"Token type (Bearer)"},"client_type":{"type":"string","enum":["jwt","access_token","refresh_token"],"description":"Type of token (jwt=user JWT, access_token=PAT-derived, refresh_token)"},"token_format":{"type":"string","enum":["jwt"],"description":"Format of the token"},"sub":{"type":"string","description":"Subject (user email or token type)"},"iss":{"type":"string","description":"Issuer"},"exp":{"type":"integer","description":"Expiration time (Unix timestamp)"},"iat":{"type":"integer","description":"Issued at time (Unix timestamp)"},"scope":{"$ref":"#/components/schemas/PATScopes"},"tenants":{"type":"array","items":{"type":"string"}},"pat_hash":{"type":"string","description":"PAT hash (for refresh tokens only)"},"reason":{"type":"string","description":"Reason for inactive status (expired)"}}},"TokenExchangeResponse":{"type":"object","description":"Response from the token exchange endpoint. For PAT-derived tokens: access tokens include user-like claims plus scopes for granular authorization. For user tokens: access tokens include standard user claims (sub, email, displayName, roles, tenantIds, delegatedAccess).","properties":{"access_token":{"type":"string","description":"JWT access token. PAT-derived tokens contain: iss, sub (email), email, displayName, roles, tenantIds, delegatedAccess, scopes. User tokens contain: iss, sub (email), email, displayName, roles, tenantIds, delegatedAccess"},"refresh_token":{"type":"string","description":"JWT refresh token (airbrx_refresh_...) - only returned for pat_exchange grant type, not for refresh_token grant type"},"token_type":{"type":"string","enum":["Bearer"],"description":"Token type"},"expires_in":{"type":"integer","description":"Access token lifetime in seconds"},"scope":{"$ref":"#/components/schemas/PATScopes","description":"Scopes inherited from the PAT - only returned for pat_exchange grant type, not for user token refresh"},"tenants":{"type":"array","items":{"type":"string"},"description":"Tenants inherited from the PAT - only returned for pat_exchange grant type, not for user token refresh"}},"required":["access_token","token_type","expires_in"]},"AvailablePermissions":{"type":"object","description":"Permissions available for the authenticated user to grant to a PAT","properties":{"permissions":{"$ref":"#/components/schemas/PATScopes"},"tenants":{"type":"array","items":{"type":"string"},"description":"Tenants the user can grant access to"}}},"GrantDelegation":{"type":"object","description":"Delegation chain information for a permission grant","properties":{"isRoot":{"type":"boolean","description":"True if this is a level-1 grant from a base admin"},"rootAdminEmail":{"type":"string","format":"email","description":"Email of the base admin at the root of the delegation chain"},"rootGrantId":{"type":"string","nullable":true,"description":"ID of the parent grant (null for root grants)"},"canDelegate":{"type":"boolean","description":"Whether the grantee can create sub-grants (always false for level-2)"},"childGrantIds":{"type":"array","items":{"type":"string"},"description":"IDs of grants created by this grantee using delegation rights"}}},"PermissionGrant":{"type":"object","description":"A delegated permission grant","properties":{"id":{"type":"string","description":"Unique grant ID","example":"grant_abc123def456"},"grantorEmail":{"type":"string","format":"email","description":"Email of user who created this grant"},"granteeEmail":{"type":"string","format":"email","description":"Email of user receiving the grant"},"tenantId":{"type":"string","description":"Tenant ID being granted access to"},"delegation":{"$ref":"#/components/schemas/GrantDelegation"},"permissions":{"type":"object","description":"Permissions granted (role or fine-grained scopes)","properties":{"role":{"type":"string","description":"Role granted (guest, cacheadmin, rulesadmin, admin)","enum":["guest","cacheadmin","rulesadmin","admin"]},"scopes":{"$ref":"#/components/schemas/PATScopes"}}},"createdAt":{"type":"string","format":"date-time","description":"When the grant was created"},"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"When the grant expires (null = no expiration)"},"revokedAt":{"type":"string","format":"date-time","nullable":true,"description":"When the grant was revoked (null = active)"},"revokedBy":{"type":"string","format":"email","nullable":true,"description":"Email of user who revoked the grant"},"revokedReason":{"type":"string","nullable":true,"description":"Reason for revocation (direct, cascade, elevated)"},"notes":{"type":"string","nullable":true,"description":"Optional notes about the grant"}},"example":{"id":"grant_abc123def456","grantorEmail":"alice@company.com","granteeEmail":"bob@example.com","tenantId":"acme.app.airbrx.com","delegation":{"isRoot":true,"rootAdminEmail":"alice@company.com","rootGrantId":null,"canDelegate":true,"childGrantIds":["grant_xyz789"]},"permissions":{"role":"guest"},"createdAt":"2025-01-15T10:00:00.000Z","expiresAt":"2026-01-15T10:00:00.000Z","revokedAt":null,"revokedBy":null,"notes":"Temporary access for project review"}},"GrantCreateRequest":{"type":"object","description":"Request body for creating a permission grant","required":["granteeEmail","tenantId","role"],"properties":{"granteeEmail":{"type":"string","format":"email","description":"Email of user to grant access to"},"tenantId":{"type":"string","description":"Tenant ID to grant access to"},"role":{"type":"string","description":"Role to grant","enum":["guest","cacheadmin","rulesadmin","admin"]},"canDelegate":{"type":"boolean","description":"Whether grantee can create sub-grants (only valid for base admins)","default":false},"expiresAt":{"type":"string","format":"date-time","description":"Optional expiration date"},"notes":{"type":"string","description":"Optional notes about the grant"},"scopes":{"$ref":"#/components/schemas/PATScopes","description":"Optional fine-grained scopes (alternative to role)"}},"example":{"granteeEmail":"bob@example.com","tenantId":"acme.app.airbrx.com","role":"guest","canDelegate":true,"expiresAt":"2026-01-15T10:00:00.000Z","notes":"Temporary access for Q1 project"}},"GrantUpdateRequest":{"type":"object","description":"Request body for updating a permission grant","properties":{"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"New expiration date (null to remove expiration)"},"notes":{"type":"string","nullable":true,"description":"Updated notes"},"canDelegate":{"type":"boolean","description":"Update delegation rights (only for level-1 grants, requires admin)"}},"example":{"expiresAt":"2026-06-15T10:00:00.000Z","notes":"Extended for phase 2"}},"Account":{"type":"object","description":"Account representing a billing entity","properties":{"accountId":{"type":"string","format":"uuid","description":"Unique account identifier"},"name":{"type":"string","description":"Account name (globally unique)","minLength":1,"maxLength":255},"status":{"type":"string","enum":["active","suspended","deleted"],"description":"Account status"},"plan":{"type":"string","enum":["free","starter","professional","enterprise"],"description":"Subscription plan"},"contacts":{"type":"object","properties":{"primary":{"type":"string","format":"email","description":"Primary contact email"},"billing":{"type":"string","format":"email","description":"Billing contact email"}},"required":["primary"]},"settings":{"type":"object","properties":{"maxTenants":{"type":"integer","nullable":true,"description":"Maximum tenants allowed (null for unlimited)"},"defaultStorageRegion":{"type":"string","description":"Default AWS region for storage"},"defaultCacheBucket":{"type":"string","description":"Default S3 bucket for cache"},"defaultLogBucket":{"type":"string","description":"Default S3 bucket for logs"}}},"registeredDomains":{"type":"array","items":{"type":"string"},"description":"Domain names registered to this account (e.g., ['acme.com', 'acme-analytics.com'])"},"isSystemAccount":{"type":"boolean","description":"System accounts cannot be modified or deleted","default":false},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"version":{"type":"string","description":"Schema version"}},"required":["accountId","name","status","plan","contacts"]},"AccountCreate":{"type":"object","description":"Request body for creating an account","properties":{"name":{"type":"string","description":"Account name (globally unique)","minLength":1,"maxLength":255},"plan":{"type":"string","enum":["free","starter","professional","enterprise"],"description":"Subscription plan"},"contacts":{"type":"object","properties":{"primary":{"type":"string","format":"email","description":"Primary contact email"},"billing":{"type":"string","format":"email","description":"Billing contact email"}},"required":["primary"]},"settings":{"type":"object","properties":{"maxTenants":{"type":"integer","nullable":true},"defaultStorageRegion":{"type":"string"}}}},"required":["name","plan","contacts"],"example":{"name":"Acme Corporation","plan":"enterprise","contacts":{"primary":"admin@acme.com","billing":"billing@acme.com"},"settings":{"maxTenants":10,"defaultStorageRegion":"us-east-1"}}},"TenantCreate":{"type":"object","description":"Request body for creating a tenant. Server generates tenantId, createdAt, updatedAt, and version. For user-JWT callers, the server auto-fills accountId from the JWT accounts claim (when the user has exactly one account) and primaryFqdn as a 3-word slug under the gateway base URL (AIR-743).","required":["tenantName","dataAdapter"],"properties":{"tenantName":{"type":"string","description":"Display name (unique within account)","minLength":1,"maxLength":255},"accountId":{"type":"string","format":"uuid","description":"Parent account reference (must exist and be active)"},"primaryFqdn":{"type":"string","description":"Main FQDN for request routing (globally unique, checked against tombstones)"},"aliasFqdns":{"type":"array","items":{"type":"string"},"description":"Additional FQDNs that route to this tenant"},"dataAdapter":{"type":"object","description":"Data warehouse connection configuration","required":["type","server_hostname"],"properties":{"type":{"type":"string","enum":["databricks","snowflake"],"description":"Warehouse backend type"},"server_hostname":{"type":"string","description":"Backend warehouse hostname"},"http_path":{"type":"string","description":"Databricks SQL endpoint path"},"cloudFilesBaseUrl":{"type":"string","nullable":true,"description":"CloudFile proxy base URL"}}},"storage":{"type":"object","description":"Operational data storage. Inherits from account defaults if omitted.","properties":{"type":{"type":"string","enum":["s3express","s3","filesystem"]},"bucket":{"type":"string"},"region":{"type":"string"},"endpoint":{"type":"string"}}},"logStorage":{"type":"object","description":"Log storage configuration. Inherits from account defaults if omitted.","properties":{"type":{"type":"string","enum":["s3","s3express","filesystem"]},"bucket":{"type":"string"},"region":{"type":"string"},"endpoint":{"type":"string"}}},"status":{"type":"string","enum":["active","suspended","deleted"],"default":"active","description":"Tenant status (defaults to active)"}},"example":{"tenantName":"Acme Production","accountId":"550e8400-e29b-41d4-a716-446655440000","primaryFqdn":"analytics.acme.com","aliasFqdns":["acme-prod.app.airbrx.com"],"dataAdapter":{"type":"databricks","server_hostname":"acme-prod.cloud.databricks.com","http_path":"/sql/1.0/warehouses/abc123"}}},"AccountUpdate":{"type":"object","description":"Request body for updating an account","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"status":{"type":"string","enum":["active","suspended","deleted"]},"plan":{"type":"string","enum":["free","starter","professional","enterprise"]},"contacts":{"type":"object","properties":{"primary":{"type":"string","format":"email"},"billing":{"type":"string","format":"email"}}},"settings":{"type":"object"}}},"DomainEntry":{"type":"object","description":"A domain registered to an account with audit history","required":["domain","accountId"],"properties":{"domain":{"type":"string","description":"Root domain name (e.g., acme.com)"},"accountId":{"type":"string","format":"uuid","description":"Current owning account (UUID v4)"},"registeredAt":{"type":"string","format":"date-time","description":"Current registration timestamp"},"registeredBy":{"type":"string","description":"Email of user who registered/transferred the domain"},"history":{"type":"array","description":"Audit trail of domain changes","items":{"type":"object","required":["action","accountId","timestamp","by"],"properties":{"action":{"type":"string","enum":["registered","transferred"],"description":"Type of action performed"},"accountId":{"type":"string","format":"uuid","description":"Target account ID"},"fromAccountId":{"type":"string","format":"uuid","description":"Source account ID (for transfers)"},"timestamp":{"type":"string","format":"date-time","description":"When action occurred"},"by":{"type":"string","description":"User who performed action"},"note":{"type":"string","description":"Optional description"}}}}},"example":{"domain":"acme.com","accountId":"550e8400-e29b-41d4-a716-446655440000","registeredAt":"2026-01-15T10:30:00.000Z","registeredBy":"admin@acme.com","history":[{"action":"registered","accountId":"550e8400-e29b-41d4-a716-446655440000","timestamp":"2026-01-15T10:30:00.000Z","by":"admin@acme.com","note":"Initial domain registration"}]}},"FqdnEntry":{"type":"object","description":"An FQDN assigned to a tenant with audit history","required":["fqdn","tenantId","isPrimary"],"properties":{"fqdn":{"type":"string","description":"Fully qualified domain name"},"tenantId":{"type":"string","format":"uuid","description":"Current owning tenant (UUID v4)"},"isPrimary":{"type":"boolean","description":"Whether this is the tenant's primaryFqdn","default":false},"assignedAt":{"type":"string","format":"date-time","description":"Current assignment timestamp"},"assignedBy":{"type":"string","description":"Email of user who assigned/migrated the FQDN"},"history":{"type":"array","description":"Audit trail of FQDN changes","items":{"type":"object","required":["action","tenantId","isPrimary","timestamp","by"],"properties":{"action":{"type":"string","enum":["assigned","updated","migrated","unassigned","tombstoned"],"description":"Type of action performed"},"tenantId":{"type":"string","format":"uuid","description":"Target tenant ID"},"fromTenantId":{"type":"string","format":"uuid","description":"Source tenant ID (for migrations)"},"isPrimary":{"type":"boolean","description":"Primary status at time of action"},"timestamp":{"type":"string","format":"date-time","description":"When action occurred"},"by":{"type":"string","description":"User who performed action"},"note":{"type":"string","description":"Optional description"}}}}},"example":{"fqdn":"analytics.acme.com","tenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","isPrimary":true,"assignedAt":"2026-01-15T10:30:00.000Z","assignedBy":"admin@acme.com","history":[{"action":"assigned","tenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","isPrimary":true,"timestamp":"2026-01-15T10:30:00.000Z","by":"admin@acme.com","note":"Initial assignment as primary FQDN"}]}},"FqdnTombstone":{"type":"object","description":"Permanently retired FQDN stored at fqdns/tombstones/{fqdn}.json. Tombstones are never deleted.","required":["fqdn","originalTenantId","assignedAt","removedAt"],"properties":{"fqdn":{"type":"string","description":"The retired fully qualified domain name"},"originalTenantId":{"type":"string","format":"uuid","description":"UUID of the tenant this FQDN was originally assigned to"},"assignedAt":{"type":"string","format":"date-time","description":"When the FQDN was originally assigned"},"removedAt":{"type":"string","format":"date-time","description":"When the FQDN was tombstoned"},"removedBy":{"type":"string","description":"Email or identifier of the person who retired this FQDN"},"history":{"type":"array","description":"Full audit trail including assignment and tombstoning","items":{"type":"object","properties":{"action":{"type":"string","enum":["assigned","updated","tombstoned"]},"tenantId":{"type":"string","format":"uuid"},"timestamp":{"type":"string","format":"date-time"},"by":{"type":"string"},"note":{"type":"string"}}}}},"example":{"fqdn":"analytics.acme.com","originalTenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","assignedAt":"2026-01-15T10:30:00.000Z","removedAt":"2026-06-20T14:00:00.000Z","removedBy":"admin@acme.com","history":[{"action":"assigned","tenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":"2026-01-15T10:30:00.000Z","by":"admin@acme.com","note":"Initial assignment"},{"action":"tombstoned","tenantId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":"2026-06-20T14:00:00.000Z","by":"admin@acme.com","note":"FQDN retired"}]}},"HATEOASLinks":{"type":"object","description":"HATEOAS-style navigation links","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}}},"parent":{"type":"object","properties":{"href":{"type":"string"}}},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"href":{"type":"string"}}}}}},"SuccessResponse":{"type":"object","description":"Generic success response","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Bad request (invalid parameters or JSON)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Request body must be a valid JSON object"}}}},"Unauthorized":{"description":"Authentication required or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unauthorized: missing token"}}}},"Forbidden":{"description":"Insufficient permissions for this operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"PAT does not have GET permission for path: /config/tenants"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Not found"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Internal server error"}}}},"Conflict":{"description":"Conflict - FQDN already in use or permanently retired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"FQDN 'analytics.acme.com' was previously assigned to tenant '7c9e6679-...' and has been permanently retired"}}}}}}}