{"openapi":"3.1.0","info":{"title":"WAYSCloud Public API","version":"1.0.0","description":"Programmatic access to all WAYSCloud services. Base URL: `https://api.wayscloud.services`\n\nThe API is organized into three layers:\n\n**Account API** — Manage your profile, API keys, SSH keys, and view active services. Uses Personal Access Tokens (PAT) or OIDC.\n\n**Management API** — Provision and control resources: VPS, container apps, databases, storage buckets, DNS zones, IoT devices. Uses Service API Keys.\n\n**Service API** — Runtime operations: LLM chat (OpenAI-compatible), S3 storage, telemetry. Uses Service Keys or AWS SigV4.\n\n**Quick start:** Create a Service API Key via dashboard or `POST /v1/account/api-keys/service`, then provision resources and start using them.\n"},"servers":[{"url":"https://api.wayscloud.services","description":"Production"}],"tags":[{"name":"Account","description":"Profile, API keys, SSH keys, and service overview"},{"name":"VPS","description":"Virtual Private Servers"},{"name":"Databases","description":"PostgreSQL and MariaDB instances"},{"name":"Storage","description":"S3-compatible object storage"},{"name":"DNS","description":"DNS zones and records"},{"name":"Dynway","description":"Dynway is WAYSCloud Dynamic DNS for networks where the public IPv4 or IPv6 address can change.\n\nUse it to give a home router, office network, lab server, camera gateway, VPN endpoint, or small self-hosted service a stable hostname such as `home.dynway.eu`. A router or Dynway Desktop updates the hostname through the standard DynDNS2 protocol, while the WAYSCloud API is used to create the name and manage the updater credentials. IPv4 updates maintain the A record; IPv6 updates maintain the AAAA record.\n\nDynway has no separate account. Private WAYSCloud accounts receive Home Free and business accounts receive Business Fleet. Use `GET /v1/dyn/entitlement` to read the authenticated customer's plan and enforced limits; do not infer either from router activity.\n\nEach live hostname has one active update source. The control API below manages customer-owned Dynway names, router tokens, and desktop updater credentials; changing source is explicit and atomic. The router update endpoint is documented here as `/nic/update`, but it is served from `https://dynupdate.wayscloud.services`."},{"name":"Apps","description":"Container App Platform. Deploy and manage container apps with scaling controls, scale-to-zero, persistent storage, and HTTPS with automated certificate management."},{"name":"LLM","description":"AI language models (OpenAI-compatible). Reasoning-capable models may return extended metadata in `model_output_metadata.reasoning` containing inference traces, without changing the request format."},{"name":"SMS","description":"Send SMS messages and manage inbound keywords. Pay-as-you-go pricing."},{"name":"SMS Keywords","description":"Reserve keywords for inbound SMS routing."},{"name":"Contacts","description":"Shared contact directory for SMS, Email and other services. Manage contacts and groups for batch operations."},{"name":"Regions","description":"Available service regions"},{"name":"Domain Verification","description":"Verify domain ownership via DNS for email sending, custom hostnames, webhooks, and more."},{"name":"Verify","description":"Multi-channel OTP/2FA verification service.\n\n**Channels:** SMS, Voice (TTS), and Email\n\n**Quick Start:**\n```bash\n# SMS\ncurl -X POST https://api.wayscloud.services/v1/verify/start \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channel\": \"sms\", \"recipient\": \"+4712345678\", \"locale\": \"no\"}'\n\n# Voice (TTS call)\ncurl -X POST https://api.wayscloud.services/v1/verify/start \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channel\": \"voice\", \"recipient\": \"+4712345678\", \"locale\": \"no\"}'\n\n# Email\ncurl -X POST https://api.wayscloud.services/v1/verify/start \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channel\": \"email\", \"recipient\": \"user@example.com\", \"locale\": \"no\"}'\n\n# Check code\ncurl -X POST https://api.wayscloud.services/v1/verify/check \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"session_id\": \"...\", \"code\": \"123456\"}'\n```\n\n**Channel Details:**\n\n| Channel | Recipient | Sender |\n|---------|-----------|--------|\n| `sms` | Phone (E.164) | Configurable alphanumeric |\n| `voice` | Phone (E.164) | +47 21 53 50 30 |\n| `email` | Email address | Verified domain or WAYSCloud default |\n\n**Pricing:** 1.25 NOK per session + delivery (SMS ~0.35 NOK, Voice ~0.50 NOK, Email included)\n\n**Security:**\n- Codes hashed with SHA256 (never stored in plaintext)\n- Max 5 attempts per session\n- TTL: 60-900 seconds (configurable)\n- Tenant isolation"},{"name":"GPU Studio","description":"Generate images and short video clips using WAYSCloud GPU Studio.\n\nGPU Studio is a job-based API:\n- Create a job with an engine and input prompt\n- Poll job status until it is completed\n- Download results via URLs in output_files\n\nEngines are curated and stable identifiers. Use GET /v1/gpu/engines to discover which engines are available for your account.\n\nPricing is not hardcoded in this documentation. Always use GET /v1/gpu/pricing for the current price list (supports multiple currencies).\n\n**Authentication:** Use either `X-API-Key` header or `Authorization: Bearer <key>` for protected endpoints."},{"name":"Speech Intelligence","description":"Transcribe audio and video files using WAYSCloud Speech Intelligence.\n\n**How it works:**\n1. `POST /v1/transcript/jobs` — create a job, receive a presigned upload URL\n2. Upload your file to the presigned URL (HTTP PUT, raw file body)\n3. `POST /v1/transcript/jobs/{job_id}/upload-complete` — confirm upload and start processing\n4. `GET /v1/transcript/jobs/{job_id}` — poll until status is `ready`\n5. Read segments from the response, or use the export endpoint for SRT/VTT\n\n**Job statuses:**\n| Status | Description |\n|--------|-------------|\n| `created` | Job created, waiting for file upload |\n| `queued` | Upload confirmed, waiting for processing |\n| `processing` | Transcription in progress |\n| `ready` | Complete — segments and default exports (TXT, JSON) available |\n| `failed` | Processing failed (see `error_message`) |\n| `expired` | Upload URL expired (file was never uploaded within 1 hour) |\n\n**Default exports:** `txt` and `json` are generated automatically when a job completes. `srt` and `vtt` can be generated on demand via `POST /v1/transcript/jobs/{job_id}/export`.\n\n**Supported inputs:** Any file with an audio track — common containers include MP3, WAV, M4A, OGG, FLAC, WEBM, and MP4. Files are validated server-side using ffprobe; the file must contain at least one audio stream. Maximum file size: 2 GB.\n\n**Languages:** Set `language` to `auto` (default) for automatic detection, or specify an ISO 639-1 code (e.g., `no`, `en`, `sv`, `da`, `fr`, `de`).\n\n**Pricing:** Per-minute billing based on audio duration. See your dashboard for current rates.\n\n**Authentication:** WAYSCloud API key via `X-API-Key` header or `Authorization: Bearer <key>`. Keys are created in the WAYSCloud dashboard under Account → API Keys."},{"name":"Chatbot","description":"Integrate WAYSCloud chatbots into your applications.\n\nBuild AI-powered chatbots with your own knowledge sources, then access them programmatically from any backend, mobile app, or integration.\n\n**Quick start:**\n```bash\n# List your chatbots\ncurl https://api.wayscloud.services/v1/chatbot/api/bots \\\n  -H \"X-API-Key: wayscloud_api_xxx\"\n\n# Send a message\ncurl -X POST https://api.wayscloud.services/v1/chatbot/api/chat \\\n  -H \"X-API-Key: wayscloud_api_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"bot_id\": \"my-bot\", \"message\": \"What are your prices?\"}'\n```\n\n**Streaming:** Use `/v1/chatbot/api/chat/stream` for real-time token-by-token responses via Server-Sent Events.\n\n**Sessions:** Omit `session_id` to start a new conversation. Include it to continue an existing one.\n\n**Authentication:** API key with `chatbot` permission, sent via `X-API-Key` header.\n\n**Manage chatbots:** Create and configure chatbots, add knowledge sources, and customize behavior in the [WAYSCloud Dashboard](https://my.wayscloud.services/chatbot)."},{"name":"Live Events","description":"Managed live streaming and event broadcasting.\n\nCreate events, manage stream keys and access tokens, control lifecycle (rehearsal → live → stop), and retrieve recordings, stats, and health. A separate set of public viewer endpoints resolves watch state for watch pages and embeds.\n\n---\n\n### Management endpoints (authenticated)\n\n**Authentication:** WAYSCloud API key or Keycloak JWT via `Authorization: Bearer` header or `X-API-Key` header.\n\n**Event lifecycle:**\n\n| Transition | Endpoint | Precondition |\n|------------|----------|-------------|\n| Create event | `POST /v1/live-events/events` | — |\n| Start rehearsal | `POST /v1/live-events/events/{id}/rehearsal/start` | Status: `ready` |\n| Go live | `POST /v1/live-events/events/{id}/live/start` | Status: `ready` or `rehearsal` |\n| Stop | `POST /v1/live-events/events/{id}/live/stop` | Status: `live` or `rehearsal` |\n| Rotate stream key | `POST /v1/live-events/events/{id}/stream-key/rotate` | Not while `live` |\n| Update | `PATCH /v1/live-events/events/{id}` | Status: `draft`, `scheduled`, or `ready` |\n| Delete | `DELETE /v1/live-events/events/{id}` | Not while `live`, `ending`, or `processing` |\n\n**RTMP ingest:** `rtmp://ingest.wayscloud.services/live` with the stream key from event creation.\n\n---\n\n### Viewer endpoints (public, no authentication)\n\nResolve event watch state for watch pages, embeds, and custom players. Verify viewer access for password- or token-protected events.\n\nThe resolve endpoint returns two state fields:\n- `watch_state` — canonical viewer-facing UI state (`not_started`, `access_required`, `live`, `replay_available`, etc.)\n- `status` — internal lifecycle state for diagnostics\n\n**Access control modes:**\n\n| Mode | Behavior |\n|------|----------|\n| `public` | Anyone with the URL can watch. Playback URLs returned directly. |\n| `password` | Viewer submits password → receives access grant + playback. |\n| `token` | Viewer submits pre-shared token → receives access grant. Token consumed on playback start, not on verify. |\n| `tenant_only` | Viewer must be authenticated as a member of the same customer account. |\n\n**Watch pages:** `https://live.wayscloud.services/watch/{slug}`\n**Embed:** `https://live.wayscloud.services/embed/{slug}`\n\n---\n\n**Manage events in dashboard:** [my.wayscloud.services/live-events](https://my.wayscloud.services/live-events)"},{"name":"IP Intelligence","description":"Real-time IP threat scoring, geolocation, and network intelligence.\n\n**Authentication:** WAYSCloud API key via `X-API-Key` header. Free tier auto-provisioned on first use — no manual activation needed.\n\n**Free tier:** 1,000 requests/day. Upgrade via [dashboard](https://my.wayscloud.services).\n\n| Endpoint | Description |\n|----------|-------------|\n| `GET /v1/ip/{ip}` | Full summary (geo + network + threat + flags) |\n| `GET /v1/ip/{ip}/geo` | Geolocation + rDNS + ASN |\n| `GET /v1/ip/{ip}/threat` | Threat score + categories + flags |\n| `GET /v1/ip/threats/live` | Live threat feed |\n| `GET /v1/ip/countries/{code}` | Country intelligence |\n| `GET /v1/ip/asn/{asn}` | ASN intelligence |\n| `POST /v1/ip/report` | Submit IP abuse report |\n| `POST /v1/ip/reporters/register` | Register as abuse reporter |\n| `POST /v1/ip/reporters/verify` | Verify reporter domain (DNS TXT) |\n| `POST /v1/ip/delist` | Request IP delisting |\n\n**Docs:** [docs.wayscloud.services/api/ip-intelligence](https://docs.wayscloud.services/api/ip-intelligence)\n**Portal:** [ip.wayscloud.services](https://ip.wayscloud.services)"},{"name":"IoT Platform","description":"Manage IoT devices, fleet operations, alarm rules, telemetry, and notifications.\n\n**Authentication:** `X-API-Key` header with `iot` service permission.\n\n**Quick Start:**\n```bash\n# Register a device\ncurl -X POST https://api.wayscloud.services/v1/iot/devices \\\n  -H \"X-API-Key: wayscloud_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Sensor A\", \"device_type\": \"ESP32\"}'\n\n# List alarms\ncurl https://api.wayscloud.services/v1/iot/alarms?status=active \\\n  -H \"X-API-Key: wayscloud_xxx\"\n\n# Create webhook notification channel\ncurl -X POST https://api.wayscloud.services/v1/iot/notifications/channels \\\n  -H \"X-API-Key: wayscloud_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Ops Webhook\", \"channel_type\": \"webhook\", \"config\": {\"url\": \"https://example.com/hook\"}}'\n```\n\n**Resources:** Devices, Groups, Profiles, Rules, Alarms, Telemetry, Notifications"}],"paths":{"/v1/account/profile":{"get":{"tags":["Account"],"summary":"Get profile","description":"Retrieve your account profile with customer ID, email, and account type.","operationId":"getAccountProfile","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"Account profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountProfile"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Account not found"}}}}}}},"/v1/account/api-keys":{"get":{"tags":["Account"],"summary":"List API keys","description":"List all API keys (service keys and PATs) on your account. Use to audit keys or find keys to revoke.","operationId":"listApiKeys","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"API keys with metadata","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/APIKeyItem"},"example":[{"id":"a8b9c0d1-e2f3-4567-89ab-cdef01234567","key_prefix":"wayscloud_2xK9mPq","key_type":"service","service":"database","description":"Production database access","scopes":["database:read","database:write"],"is_active":true,"created_at":"2024-03-01T14:00:00Z","last_used_at":"2024-03-15T08:45:22Z"}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/account/api-keys/service":{"post":{"tags":["Account"],"summary":"Create service key","description":"Create a service API key for accessing databases, storage, VPS, DNS, IoT, or LLM. The full key is shown only once—store it securely.","operationId":"createServiceKey","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceKeyRequest"}}}},"responses":{"200":{"description":"API key created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyCreated"}}}},"400":{"description":"Invalid service type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid service type. Valid options: database, storage, llm-api, dns, vps"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/account/api-keys/pat":{"post":{"tags":["Account"],"summary":"Create PAT","description":"Create a Personal Access Token for account management and automation. Choose scopes carefully. The full token is shown only once.","operationId":"createPat","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePATRequest"}}}},"responses":{"200":{"description":"PAT created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyCreated"}}}},"400":{"description":"Invalid scopes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid scopes. See GET /v1/account/api-keys for valid scope names"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/account/api-keys/{key_id}":{"delete":{"tags":["Account"],"summary":"Revoke API key","description":"Permanently revoke an API key or PAT. Takes effect immediately. This action cannot be undone.","operationId":"revokeApiKey","security":[{"AccountAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Key ID from list endpoint"}],"responses":{"200":{"description":"Key revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Key not found"}}}}}}},"/v1/account/ssh-keys":{"get":{"tags":["Account"],"summary":"List SSH keys","description":"List all SSH public keys registered for VPS access. Shows fingerprint and usage count.","operationId":"listSshKeys","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"SSH keys","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SSHKeyItem"},"example":[{"id":"d1e2f3a4-b5c6-7890-abcd-ef1234567890","name":"deploy-server","fingerprint":"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8","key_type":"ssh-ed25519","created_at":"2024-02-20T11:15:00Z","vm_count":3}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}},"post":{"tags":["Account"],"summary":"Add SSH key","description":"Register a new SSH public key. Supports RSA, Ed25519, and ECDSA. Keys are automatically deployed to new VPS instances.","operationId":"createSshKey","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSSHKeyRequest"}}}},"responses":{"200":{"description":"SSH key added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSHKeyItem"}}}},"400":{"description":"Invalid key format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid SSH key format. Supported types: ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"409":{"description":"Key already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"SSH key with this fingerprint is already registered"}}}}}}},"/v1/account/ssh-keys/{key_id}":{"delete":{"tags":["Account"],"summary":"Delete SSH key","description":"Remove an SSH key from your account. Existing VPS instances keep the key until manually removed or recreated.","operationId":"deleteSshKey","security":[{"AccountAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Key ID"}],"responses":{"200":{"description":"Key deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Key not found"}}}}}}},"/v1/account/services":{"get":{"tags":["Account"],"summary":"List services","description":"Get all your active services: databases, storage buckets, VPS instances, DNS zones, IoT devices, and GlobalSIM cards.","operationId":"listServices","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"Active services","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServiceItem"},"example":[{"id":"f3a4b5c6-d7e8-9012-cdef-345678901234","service_type":"database","name":"webapp-prod-db","status":"running","region":"oslo","created_at":"2024-02-01T10:00:00Z","metadata":{"engine":"postgresql","version":"16"}}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/databases":{"post":{"summary":"Create database","description":"Create a new database (PostgreSQL or MariaDB)\n\nThis endpoint:\n1. Authenticates the customer via API key\n2. Checks database quota and permissions\n3. Creates a database and user on the local server\n4. Reports usage to the central server for billing\n5. Returns connection details\n\nRequires: 'database:create' permission\n\nSpecial behavior for internal API keys:\n- Internal keys can specify customer_id in request body to create databases on behalf of customers\n- Regular customer keys can only create databases for themselves (customer_id override is ignored)","operationId":"create_database_v1_databases_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCreateResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Database name conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database with this name already exists"}}}}},"security":[{"ServiceAuth":[]}],"tags":["Databases"]},"delete":{"summary":"Delete database","description":"Delete a database (PostgreSQL or MariaDB)\n\nThis endpoint:\n1. Authenticates the customer via API key\n2. Checks permissions\n3. Verifies database ownership\n4. Deletes the database and optionally the user\n5. Reports deletion to the central server\n\nRequires: 'database:delete' permission and database ownership","operationId":"delete_database_v1_databases_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseDeleteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseDeleteResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}],"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}":{"delete":{"summary":"Delete database","description":"Delete a database (RESTful endpoint with path parameters)\n\nThis is an alternative RESTful endpoint that accepts database info via URL path\ninstead of request body. It calls the same underlying delete logic.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    username: Optional username to delete (query parameter)\n\nRequires: 'database:delete' permission and database ownership","operationId":"delete_database_restful_v1_databases__db_type___db_name__delete","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"username","in":"query","required":false,"schema":{"type":"string","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseDeleteResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]},"patch":{"tags":["Databases"],"summary":"Update database","description":"Update database settings (scaling)\n\nCurrently supports:\n- **connection_limit**: Adjust the maximum number of simultaneous connections\n- **description**: Add or update a description/notes for this database\n\nOther scaling options (RAM, storage) require support intervention\nas they involve infrastructure changes.","operationId":"scale_database_v1_databases__db_type___db_name__patch","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseScaleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}":{"get":{"summary":"List databases by type","description":"List all databases of a specific type\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    customer_id: Optional query parameter for internal keys to filter by specific customer\n\nRequires: 'database:read' permission\n\nSpecial behavior for internal API keys:\n- Internal keys can specify customer_id query parameter to list databases for specific customer\n- Regular customer keys can only list their own databases (customer_id parameter is ignored)","operationId":"list_databases_v1_databases__db_type__get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseListResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"tags":["Databases"]}},"/v1/databases/engines":{"get":{"tags":["Databases"],"summary":"List database engines","description":"List all supported database engines\n\nReturns available database types with their versions and capabilities.\nRequires service API key.","operationId":"list_database_engines_v1_databases_engines_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseEngineList"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/databases/{db_type}/{db_name}/restart":{"post":{"tags":["Databases"],"summary":"Restart database","description":"Terminate all active connections to a database\n\nThis is useful when you need to force-disconnect all clients,\nfor example before maintenance or schema changes.\n\n**Warning:** This will immediately disconnect all active clients.\nApplications should be prepared to reconnect automatically.\n\nNote: This doesn't restart the database server itself, only\nterminates connections to this specific database.","operationId":"restart_database_connections_v1_databases__db_type___db_name__restart_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/credentials":{"get":{"tags":["Databases"],"summary":"Get database credentials","description":"Get database credentials metadata (without password)\n\nReturns connection info for the database without exposing the password.\nTo get a new password, use POST /credentials/rotate.\n\n**Security Note:** This endpoint intentionally does NOT return the password.\nPasswords are only shown when first created or when rotated.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"get_database_credentials_v1_databases__db_type___db_name__credentials_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCredentials"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/credentials/rotate":{"post":{"tags":["Databases"],"summary":"Rotate database credentials","description":"Rotate database password and return new credentials\n\nGenerates a new secure password for the database user and updates\nit in both the database server and Vault.\n\n**Warning:** This will invalidate the old password immediately.\nAll existing connections using the old password will fail.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nReturns:\n    New credentials including the new password and connection string","operationId":"rotate_database_credentials_v1_databases__db_type___db_name__credentials_rotate_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCredentialsResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/info":{"get":{"summary":"Get database info","description":"Get detailed information about a database\n\nReturns:\n- Database size (MB)\n- Current connection count\n- Connection limit\n- Owner\n- Creation timestamp (if available from Vault)\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"get_database_info_v1_databases__db_type___db_name__info_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseInfoResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/firewall":{"post":{"tags":["Databases"],"summary":"Add firewall rule","description":"Add an IP address to the firewall whitelist for a specific database\n\nCreates a UFW firewall rule to allow the specified IP address to connect\nto this database. Rules are applied immediately at the network level.\n\n**Path Parameters:**\n- db_type: Database type (postgresql or mariadb)\n- db_name: Technical database name (e.g., cust_abc123_pg_20251109_xyz)\n\n**Request Body:**\n- ip_address: IPv4 address or CIDR block (e.g., \"192.168.1.1\" or \"10.0.0.0/24\")\n- description: Optional description for the rule\n\n**Note:** The database must belong to the authenticated customer.","operationId":"add_firewall_rule_v1_databases__db_type___db_name__firewall_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirewallAddRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirewallRuleResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}},"get":{"tags":["Databases"],"summary":"List firewall rules","description":"List all firewall rules for a specific database\n\nReturns all IP addresses that are whitelisted for this database.\n\n**Path Parameters:**\n- db_type: Database type (postgresql or mariadb)\n- db_name: Technical database name\n\n**Note:** The database must belong to the authenticated customer.","operationId":"get_database_firewall_rules_v1_databases__db_type___db_name__firewall_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirewallListResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/firewall/{rule_id}":{"delete":{"tags":["Databases"],"summary":"Delete firewall rule","description":"Remove a specific firewall rule from a database\n\nRemoves the UFW firewall rule and deletes it from the database.\nThe rule is verified to be removed before updating the database.\n\n**Path Parameters:**\n- db_type: Database type (postgresql or mariadb)\n- db_name: Technical database name\n- rule_id: UUID of the firewall rule to delete\n\n**Note:** The database must belong to the authenticated customer.","operationId":"delete_firewall_rule_v1_databases__db_type___db_name__firewall__rule_id__delete","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/metrics":{"get":{"tags":["Databases"],"summary":"Get database metrics","description":"Get performance metrics for a specific database\n\nReturns storage usage, connection counts, and query statistics.\nData is pulled from the central metrics database.\n\n**Note:** Metrics are collected every 5 minutes by the background collector.","operationId":"get_database_metrics_v1_databases__db_type___db_name__metrics_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseMetrics"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/quota":{"get":{"tags":["Databases"],"summary":"Get database quota","description":"Get database quota and usage summary for customer\n\nShows RAM allocation, storage usage, and estimated monthly cost.","operationId":"get_database_quota_v1_quota_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseQuota"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/databases/history":{"get":{"tags":["Databases"],"summary":"List database history","description":"List database history (for billing and audit)\n\nShows all databases ever created by the customer, including deleted ones.\nUseful for billing reconciliation and audit purposes.","operationId":"list_database_history_v1_databases_history_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseHistory"}}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/databases/{db_type}/{db_name}/snapshots":{"post":{"summary":"Create snapshot","description":"Create a snapshot (backup) of a database\n\nThis creates an immediate backup of the database.\nSnapshots are stored on disk and can be restored later.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    request: Snapshot creation request (optional description)\n\nRequires: 'database:read' permission and database ownership","operationId":"create_snapshot_v1_databases__db_type___db_name__snapshots_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotCreateResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]},"get":{"summary":"List snapshots","description":"List all snapshots for a database\n\nReturns all snapshots (backups) created for this database,\nsorted by creation time (newest first).\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"list_snapshots_v1_databases__db_type___db_name__snapshots_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotListResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/restore":{"post":{"summary":"Restore from snapshot","description":"Restore a database from a snapshot\n\nThis replaces the current database contents with the snapshot.\nWARNING: This operation cannot be undone! Consider creating a snapshot first.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    request: Restore request (snapshot_id, optional target_db_name)\n\nRequires: 'database:update' permission and database ownership","operationId":"restore_snapshot_v1_databases__db_type___db_name__restore_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotRestoreRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotRestoreResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/snapshots/{snapshot_id}":{"delete":{"summary":"Delete snapshot","description":"Delete a snapshot\n\nPermanently deletes a snapshot file from disk.\nThis operation cannot be undone.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    snapshot_id: Snapshot ID to delete\n\nRequires: 'database:delete' permission and database ownership","operationId":"delete_snapshot_v1_databases__db_type___db_name__snapshots__snapshot_id__delete","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","title":"Snapshot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/backup-policy":{"get":{"summary":"Get backup policy","description":"Get backup policy for a database\n\nReturns the automated backup configuration for this database.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"get_backup_policy_v1_databases__db_type___db_name__backup_policy_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicyResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]},"put":{"summary":"Set backup policy","description":"Set backup policy for a database\n\nConfigure automated backups for this database.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    request: Backup policy configuration\n\nRequires: 'database:update' permission and database ownership","operationId":"set_backup_policy_v1_databases__db_type___db_name__backup_policy_put","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicyResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/vps":{"get":{"tags":["VPS"],"summary":"List VPS","description":"Get all VPS instances with hostname, IP addresses, power state, and resource allocation.","operationId":"listVps","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"VPS instances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["VPS"],"summary":"Create VPS","description":"Provision a new VPS. Takes 1-3 minutes. Include SSH keys for passwordless access.","operationId":"createVps","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["hostname","plan_code","region","os_template"],"properties":{"hostname":{"type":"string","description":"Server hostname (FQDN recommended)","example":"web01.example.com"},"plan_code":{"type":"string","description":"VPS plan code (from /v1/vps/plans)","example":"vps-4gb-2cpu"},"region":{"type":"string","description":"Datacenter region (see GET /v1/vps/regions for available options)","example":"oslo"},"os_template":{"type":"string","description":"Operating system template (from /v1/vps/os-templates)","example":"ubuntu-22.04"},"display_name":{"type":"string","description":"User-friendly name for dashboard display","example":"Production Web Server"},"ssh_keys":{"type":"array","items":{"type":"string"},"description":"SSH public keys for server access (recommended)"}}},"example":{"hostname":"web01.example.com","plan_code":"vps-4gb-2cpu","region":"oslo","os_template":"ubuntu-22.04","display_name":"Production Web Server","ssh_keys":["ssh-ed25519 AAAA... user@laptop"]}}}},"responses":{"201":{"description":"VPS provisioning started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSCreated"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid plan_code. See GET /v1/vps/plans for available options"}}}},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient balance. Top up at my.wayscloud.services/billing"}}}},"409":{"description":"Hostname conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Hostname already in use by another VPS"}}}}}}},"/v1/vps/plans/":{"get":{"tags":["VPS"],"summary":"List plans","description":"Get available VPS configurations with vCPU, RAM, storage, bandwidth, and pricing.","operationId":"listVpsPlans","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available plans with specifications and pricing","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"plan_code":{"type":"string","example":"vps-4gb-2cpu"},"name":{"type":"string","example":"4 GB RAM, 2 vCPU"},"vcpus":{"type":"integer","example":2},"ram_gb":{"type":"integer","example":4},"storage_gb":{"type":"integer","example":80},"bandwidth_tb":{"type":"number","example":4.0},"price":{"type":"number","description":"Monthly price in account currency","example":199.0},"currency":{"type":"string","description":"Price currency (from account settings)","example":"NOK"}}},"example":[{"plan_code":"vps-2gb-1cpu","name":"2 GB RAM, 1 vCPU","vcpus":1,"ram_gb":2,"storage_gb":40,"bandwidth_tb":2.0,"price":99.0,"currency":"NOK"},{"plan_code":"vps-4gb-2cpu","name":"4 GB RAM, 2 vCPU","vcpus":2,"ram_gb":4,"storage_gb":80,"bandwidth_tb":4.0,"price":199.0,"currency":"NOK"},{"plan_code":"vps-8gb-4cpu","name":"8 GB RAM, 4 vCPU","vcpus":4,"ram_gb":8,"storage_gb":160,"bandwidth_tb":8.0,"price":399.0,"currency":"NOK"}]}}}}}}},"/v1/vps/os-templates":{"get":{"tags":["VPS"],"summary":"List OS templates","description":"Get available operating systems for VPS provisioning: Ubuntu, Debian, Rocky, AlmaLinux, and Windows Server.","operationId":"listVpsOsTemplates","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available OS templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"ubuntu-22.04"},"name":{"type":"string","example":"Ubuntu 22.04 LTS"},"family":{"type":"string","example":"ubuntu"},"version":{"type":"string","example":"22.04"},"is_windows":{"type":"boolean","example":false}}},"example":[{"id":"ubuntu-24.04","name":"Ubuntu 24.04 LTS","family":"ubuntu","version":"24.04","is_windows":false},{"id":"debian-12","name":"Debian 12","family":"debian","version":"12","is_windows":false},{"id":"rocky-9","name":"Rocky Linux 9","family":"rocky","version":"9","is_windows":false},{"id":"windows-server-2025","name":"Windows Server 2025","family":"windows","version":"2025","is_windows":true}]}}}}}}},"/v1/vps/regions":{"get":{"tags":["VPS"],"summary":"List regions","description":"Get available datacenter regions. Choose the region closest to your users for best latency.","operationId":"listVpsRegions","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available regions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"oslo"},"name":{"type":"string","example":"Oslo, Norway"},"country":{"type":"string","example":"NO"},"available":{"type":"boolean","example":true}}},"example":[{"id":"oslo","name":"Oslo, Norway","country":"NO","available":true},{"id":"stockholm","name":"Stockholm, Sweden","country":"SE","available":true},{"id":"frankfurt","name":"Frankfurt, Germany","country":"DE","available":true}]}}}}}}},"/v1/vps/{vps_id}":{"get":{"tags":["VPS"],"summary":"Get VPS","description":"Get complete VPS details: hostname, IP addresses, OS, plan, and configuration.","operationId":"getVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSInstance"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/status":{"get":{"tags":["VPS"],"summary":"Get status","description":"Check power state (running/stopped), uptime, and health indicators.","operationId":"getVpsStatus","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSStatus"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/start":{"post":{"tags":["VPS"],"summary":"Start VPS","description":"Power on a stopped VPS. Accessible within 30-60 seconds.","operationId":"startVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS starting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/stop":{"post":{"tags":["VPS"],"summary":"Stop VPS","description":"Gracefully shut down a running VPS. Stopped VPS still incurs storage costs.","operationId":"stopVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS stopping","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/reboot":{"post":{"tags":["VPS"],"summary":"Reboot VPS","description":"Graceful reboot. Back online within 1-2 minutes.","operationId":"rebootVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS rebooting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/snapshots":{"get":{"tags":["VPS Snapshots"],"summary":"List snapshots","description":"List point-in-time snapshots for a VPS. Snapshots are stored on the same server node — they are **not** off-site backups.","operationId":"listVpsSnapshots","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Snapshot list","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"vps_id":{"type":"string"},"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/VPSSnapshot"}}}}}}},"400":{"description":"Snapshots not supported for this VPS"},"404":{"description":"VPS not found"}}},"post":{"tags":["VPS Snapshots"],"summary":"Create snapshot","description":"Create a point-in-time snapshot of the VPS disk. Maximum 10 per VPS. Names are sanitized to alphanumeric + underscore.","operationId":"createVpsSnapshot","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSSnapshotCreate"}}}},"responses":{"201":{"description":"Snapshot created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSSnapshot"}}}},"400":{"description":"Unsupported disk format or name too short"},"409":{"description":"Duplicate name or VPS not provisioned"},"429":{"description":"Snapshot limit reached (max 10)"}}}},"/v1/vps/{vps_id}/snapshots/{snapshot_name}":{"delete":{"tags":["VPS Snapshots"],"summary":"Delete snapshot","description":"Delete a snapshot from the hypervisor and database.","operationId":"deleteVpsSnapshot","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"snapshot_name","in":"path","required":true,"schema":{"type":"string"},"description":"Snapshot name"}],"responses":{"200":{"description":"Snapshot deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"Snapshot not found"}}}},"/v1/vps/{vps_id}/snapshots/{snapshot_name}/rollback":{"post":{"tags":["VPS Snapshots"],"summary":"Rollback to snapshot","description":"**WARNING: Destructive operation.** Reverts the VPS disk to a previous snapshot state. All data written after the snapshot will be permanently lost. The VPS must be stopped.","operationId":"rollbackVpsSnapshot","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"snapshot_name","in":"path","required":true,"schema":{"type":"string"},"description":"Snapshot name"}],"responses":{"200":{"description":"Rollback completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"409":{"description":"VPS must be stopped or snapshot not available"}}}},"/v1/vps/{vps_id}/backup-policy":{"get":{"tags":["VPS Backups"],"summary":"Get backup policy","description":"Get the automatic backup schedule for a VPS.","operationId":"getVpsBackupPolicy","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Backup policy (or null if not configured)","content":{"application/json":{"schema":{"type":"object","properties":{"policy":{"$ref":"#/components/schemas/VPSBackupPolicy"},"vps_id":{"type":"string"}}}}}}}},"put":{"tags":["VPS Backups"],"summary":"Set backup policy","description":"Configure automatic off-site backups. Backups are stored in secure cloud storage, separate from the VPS server.","operationId":"setVpsBackupPolicy","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSBackupPolicySet"}}}},"responses":{"200":{"description":"Policy saved","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"policy_id":{"type":"string"},"next_backup_at":{"type":"string","format":"date-time"}}}}}}}},"delete":{"tags":["VPS Backups"],"summary":"Delete backup policy","description":"Remove the automatic backup schedule. Existing backups are not deleted.","operationId":"deleteVpsBackupPolicy","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Policy deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"No policy found"}}}},"/v1/vps/{vps_id}/backups":{"get":{"tags":["VPS Backups"],"summary":"List backups","description":"List off-site backups for a VPS. Unlike snapshots, backups survive hardware failure.","operationId":"listVpsBackups","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Backup list","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"vps_id":{"type":"string"},"backups":{"type":"array","items":{"$ref":"#/components/schemas/VPSBackup"}}}}}}}}},"post":{"tags":["VPS Backups"],"summary":"Trigger manual backup","description":"Start an immediate off-site backup. The backup runs asynchronously — poll GET /backups to check progress.","operationId":"createVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"202":{"description":"Backup queued","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"backup_id":{"type":"string"},"status":{"type":"string"}}}}}},"409":{"description":"Backup already in progress"}}}},"/v1/vps/{vps_id}/backups/{backup_id}":{"delete":{"tags":["VPS Backups"],"summary":"Delete backup","description":"Permanently delete a backup from off-site storage.","operationId":"deleteVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"backup_id","in":"path","required":true,"schema":{"type":"string"},"description":"Backup ID"}],"responses":{"200":{"description":"Backup deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"Backup not found"},"409":{"description":"Cannot delete backup in progress"}}}},"/v1/vps/{vps_id}/backups/{backup_id}/restore":{"post":{"tags":["VPS Backups"],"summary":"Restore from backup","description":"**WARNING: Destructive operation.** The current VPS disk will be completely replaced with backup data. All changes after the backup will be lost. The VPS must be stopped.","operationId":"restoreVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"backup_id","in":"path","required":true,"schema":{"type":"string"},"description":"Backup ID"}],"responses":{"200":{"description":"Restore queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"409":{"description":"VPS must be stopped or backup not available"}}}},"/v1/vps/{vps_id}/backup-usage":{"get":{"tags":["VPS Backups"],"summary":"Backup storage usage","description":"Get total backup storage consumed by this VPS.","operationId":"getVpsBackupUsage","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Usage metrics","content":{"application/json":{"schema":{"type":"object","properties":{"vps_id":{"type":"string"},"total_backups":{"type":"integer"},"total_size_gb":{"type":"number"}}}}}}}}},"/v1/vps/{vps_id}/backups/{backup_id}/download":{"get":{"tags":["VPS Backups"],"summary":"Download backup","description":"Get a time-limited download URL (1 hour). Served via storage.wayscloud.services.","operationId":"downloadVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"backup_id","in":"path","required":true,"schema":{"type":"string"},"description":"Backup ID"}],"responses":{"200":{"description":"Download URL","content":{"application/json":{"schema":{"type":"object","properties":{"download_url":{"type":"string","format":"uri"},"expires_in":{"type":"integer","description":"Seconds until URL expires"},"size_bytes":{"type":"integer"},"filename":{"type":"string"}}}}}},"409":{"description":"Backup not available"}}}},"/v1/apps/plans":{"get":{"tags":["Apps"],"summary":"List app plans","description":"Get available App Platform plans with CPU, memory, storage, and pricing information.","operationId":"listAppPlans","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available plans","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppPlan"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/regions":{"get":{"tags":["Apps"],"summary":"List app regions","description":"Get available regions for App Platform with availability status.","operationId":"listAppRegions","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available regions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppRegion"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps":{"get":{"tags":["Apps"],"summary":"List apps","description":"Get a paginated list of all apps for the authenticated account.","operationId":"listApps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"page_size","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100},"description":"Items per page"}],"responses":{"200":{"description":"Paginated app list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppList"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Apps"],"summary":"Create app","description":"Create a new container app. Only `name` is required — all other fields have sensible defaults.","operationId":"createApp","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":3,"maxLength":100,"description":"Display name for the app"},"slug":{"type":"string","minLength":3,"maxLength":63,"nullable":true,"description":"URL slug. Auto-generated from name if omitted. Lowercase letters, numbers, and hyphens only."},"region":{"type":"string","default":"no","description":"Region code (from GET /v1/apps/regions)"},"plan":{"type":"string","nullable":true,"description":"Plan ID (from GET /v1/apps/plans). Uses account default if omitted."},"port":{"type":"integer","default":8080,"minimum":1,"maximum":65535,"description":"Port the app listens on"},"health_check_path":{"type":"string","default":"/health","maxLength":255,"description":"Health check endpoint path"},"env_vars":{"type":"object","additionalProperties":{"type":"string"},"nullable":true,"description":"Environment variables as key-value pairs"}}},"example":{"name":"invoice-api-prod","region":"no","plan":"app-starter","port":3000,"env_vars":{"NODE_ENV":"production"}}}}},"responses":{"201":{"description":"App created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"App quota exceeded for your plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/apps/{app_id}":{"get":{"tags":["Apps"],"summary":"Get app","description":"Get full details of a specific app including configuration, deployment status, and scaling settings.","operationId":"getApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Apps"],"summary":"Update app","description":"Update app configuration. All fields are optional — only provided fields are updated.\n\n**Mutable fields:** `name`, `port`, `health_check_path`, `min_instances`, `max_instances`, `env_vars`, `scale_to_zero_enabled`, `idle_timeout_minutes`\n\n**Not mutable:** `plan`, `region`, `slug` cannot be changed after creation.\n\n**Warning:** Providing `env_vars` replaces the entire environment variable set for the app. To add a single variable, first GET the current app, merge your changes, then PATCH with the full set.","operationId":"updateApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":3,"maxLength":100},"port":{"type":"integer","minimum":1,"maximum":65535},"health_check_path":{"type":"string","maxLength":255},"min_instances":{"type":"integer","minimum":0,"maximum":10,"description":"Minimum instances. Set to 0 to allow scale-to-zero."},"max_instances":{"type":"integer","minimum":1,"maximum":10},"env_vars":{"type":"object","additionalProperties":{"type":"string"},"description":"Replaces the entire environment variable set. Not a merge — omitted variables are removed."},"scale_to_zero_enabled":{"type":"boolean","description":"Enable scale-to-zero when app is idle"},"idle_timeout_minutes":{"type":"integer","minimum":1,"maximum":60,"description":"Minutes of inactivity before scaling to zero"}}},"example":{"port":3000,"min_instances":1,"max_instances":3,"env_vars":{"NODE_ENV":"production","LOG_LEVEL":"info"}}}}},"responses":{"200":{"description":"Updated app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Apps"],"summary":"Delete app","description":"Soft-delete an app. The app is marked as deleted and permanent cleanup happens asynchronously according to platform retention policies.","operationId":"deleteApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/deploy/image":{"post":{"tags":["Apps"],"summary":"Deploy from image","description":"Deploy the app from a container image. The deployment is asynchronous — poll GET /v1/apps/{app_id} for status updates.\n\n**Supported registries include:** `registry.wayscloud.services/`, `ghcr.io/`, `docker.io/`, and Docker Hub shorthand (e.g. `nginx:latest`, `traefik/whoami`).","operationId":"deployAppImage","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["image_uri"],"properties":{"image_uri":{"type":"string","minLength":5,"maxLength":500,"description":"Container image URI"}}},"example":{"image_uri":"ghcr.io/acme-corp/invoice-api:v2.4.1"}}}},"responses":{"200":{"description":"Deployment initiated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppDeployResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid image URI or unsupported registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"No host available in the requested region","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/start":{"post":{"tags":["Apps"],"summary":"Start app","description":"Start a stopped app or wake it from scale-to-zero state.","operationId":"startApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App starting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/stop":{"post":{"tags":["Apps"],"summary":"Stop app","description":"Stop all running instances. The app can be restarted later.","operationId":"stopApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App stopping","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/restart":{"post":{"tags":["Apps"],"summary":"Restart app","description":"Restart the app by recreating running instances.","operationId":"restartApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App restarting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/logs":{"get":{"tags":["Apps"],"summary":"Get app logs","description":"Returns the most recent container log output (stdout/stderr). This is live container output, not a historical log archive. Logs may be empty for stopped or idle apps. For persistent logging, configure your app to ship logs to an external service.","operationId":"getAppLogs","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"},{"name":"lines","in":"query","schema":{"type":"integer","default":100,"minimum":1,"maximum":1000},"description":"Number of log lines to retrieve"}],"responses":{"200":{"description":"Log output","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppLogs"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/storage/buckets":{"get":{"tags":["Storage"],"summary":"List buckets","description":"Get all storage buckets with name, creation date, and usage statistics.","operationId":"listBuckets","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Buckets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageBucketList"}}}}}},"post":{"tags":["Storage"],"summary":"Create bucket","description":"Create a new bucket. Names: 3-63 chars, lowercase/numbers/hyphens, globally unique, cannot be reused after deletion.\n\n**Storage Tiers:**\n- `standard` (default): Reliable storage for general use\n- `enterprise`: High-performance storage with dedicated infrastructure and enhanced durability","operationId":"createBucket","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bucket_name"],"properties":{"bucket_name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$","minLength":3,"maxLength":63,"description":"Unique bucket name (lowercase, numbers, hyphens)","example":"my-app-uploads"},"tier":{"type":"string","default":"standard","enum":["standard","enterprise"],"description":"Storage tier: standard (general use) or enterprise (dedicated infrastructure, enhanced performance)"},"region":{"type":"string","default":"no-oslo-1","description":"Storage region (currently only no-oslo-1 available)","example":"no-oslo-1"}}},"example":{"bucket_name":"my-app-uploads","tier":"standard"}}}},"responses":{"201":{"description":"Bucket created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageBucketCreated"}}}},"400":{"description":"Invalid bucket name format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid bucket name. Must be 3-63 chars, lowercase, numbers, and hyphens only."}}}},"409":{"description":"Bucket name conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket name already taken or was previously used. Choose a different name."}}}}}}},"/v1/storage/buckets/{bucket_name}":{"get":{"tags":["Storage"],"summary":"Get bucket","description":"Get bucket details: name, creation date, object count, and storage used.","operationId":"getBucket","security":[{"ServiceAuth":[]}],"parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string"},"description":"Bucket name"}],"responses":{"200":{"description":"Bucket details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageBucket"}}}},"404":{"description":"Bucket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket not found"}}}}}},"delete":{"tags":["Storage"],"summary":"Delete bucket","description":"Delete a bucket permanently. Must be empty first. Deleted names cannot be reused.","operationId":"deleteBucket","security":[{"ServiceAuth":[]}],"parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string"},"description":"Name of the bucket to delete"}],"responses":{"200":{"description":"Bucket deleted permanently","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Bucket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket not found"}}}},"409":{"description":"Bucket is not empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket is not empty. Delete all objects first."}}}}}}},"/v1/storage/credentials":{"get":{"tags":["Storage"],"summary":"Get S3 credentials","description":"Get Access Key and Secret Key for S3-compatible access. Endpoint: `storage.wayscloud.services`, Region: `eu-north-1`.","operationId":"getStorageCredentials","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"S3 credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/S3Credentials"}}}}}}},"/v1/dns/zones":{"get":{"tags":["DNS"],"summary":"List zones","description":"Get all DNS zones with nameservers and record counts.","operationId":"listDnsZones","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"DNS zones","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZoneList"}}}}}},"post":{"tags":["DNS"],"summary":"Create zone","description":"Create a DNS zone for a domain. Update your registrar nameservers after creation.","operationId":"createDnsZone","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Domain name","example":"example.com"}},"example":{"domain":"example.com"}}}}},"responses":{"201":{"description":"Zone created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZone"}}}},"400":{"description":"Invalid domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid domain format"}}}}}}},"/v1/dns/zones/{zone_name}":{"get":{"tags":["DNS"],"summary":"Get zone","description":"Get zone details: nameservers, DNSSEC status, and record count.","operationId":"getDnsZone","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name","example":"example.com"}],"responses":{"200":{"description":"Zone details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZone"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}},"delete":{"tags":["DNS"],"summary":"Delete zone","description":"Delete a zone and all its records permanently. Irreversible.","operationId":"deleteDnsZone","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"responses":{"200":{"description":"Zone deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/records":{"get":{"tags":["DNS"],"summary":"List records","description":"Get all DNS records (A, AAAA, CNAME, MX, TXT, etc.) with values and TTLs.","operationId":"listDnsRecords","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"responses":{"200":{"description":"DNS records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSRecordList"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}},"post":{"tags":["DNS"],"summary":"Create record","description":"Add a DNS record. Supported types: A, AAAA, CNAME, MX, TXT, SRV, NS, CAA. Propagates within 5 minutes.","operationId":"createDnsRecord","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["record_type","host","record"],"properties":{"record_type":{"type":"string","enum":["A","AAAA","CNAME","MX","TXT","SRV","NS","CAA","PTR"],"example":"A"},"host":{"type":"string","description":"Hostname","example":"www"},"record":{"type":"string","description":"Value","example":"192.0.2.1"},"ttl":{"type":"integer","default":3600,"example":3600},"priority":{"type":"integer","description":"For MX/SRV","example":10}},"example":{"record_type":"A","host":"www","record":"192.0.2.1","ttl":3600}}}}},"responses":{"201":{"description":"Record created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSRecord"}}}},"400":{"description":"Invalid format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid format"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/records/{record_id}":{"put":{"tags":["DNS"],"summary":"Update record","description":"Update record value, TTL, or priority.","operationId":"updateDnsRecord","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string"},"description":"Record ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"host":{"type":"string","example":"www"},"record":{"type":"string","example":"192.0.2.50"},"ttl":{"type":"integer","example":1800},"priority":{"type":"integer","example":10}},"example":{"record":"192.0.2.50","ttl":1800}}}}},"responses":{"200":{"description":"Record updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSRecord"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}},"delete":{"tags":["DNS"],"summary":"Delete record","description":"Delete a DNS record from a zone.","operationId":"deleteDnsRecord","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string"},"description":"Record ID"}],"responses":{"200":{"description":"Record deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}}},"/v1/dns/zones/{zone_name}/dnssec/activate":{"post":{"tags":["DNS"],"summary":"Activate DNSSEC","description":"Enable DNSSEC to protect against DNS spoofing. Configure returned DS records at your registrar.","operationId":"activateDnssec","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"}],"responses":{"200":{"description":"DNSSEC activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSSECInfo"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/dnssec":{"delete":{"tags":["DNS"],"summary":"Deactivate DNSSEC","description":"Disable DNSSEC. Remove DS records from registrar first to avoid resolution failures.","operationId":"deactivateDnssec","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"}],"responses":{"200":{"description":"DNSSEC deactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/statistics":{"get":{"tags":["DNS"],"summary":"Get statistics","description":"Get query statistics: counts, response times, and geographic distribution.","operationId":"getDnsStatistics","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"}],"responses":{"200":{"description":"Zone statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSStatistics"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dyn/entitlement":{"get":{"tags":["Dynway"],"summary":"Get Dynway entitlement","description":"Return the authenticated customer's Dynway plan and currently enforced limits. Private WAYSCloud accounts map to Home Free; business accounts map to Business Fleet. The result is derived from canonical WAYSCloud account state and must not be inferred from router activity.","operationId":"getDynwayEntitlement","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"Dynway plan and enforced limits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynEntitlementResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/webhook-endpoints":{"get":{"tags":["Dynway"],"summary":"List Dynway webhook endpoints","description":"List webhook endpoints owned by the authenticated Business Fleet customer. Endpoint configuration is customer-scoped; signing secrets and Vault references are never returned. Requires `dyn:read`.","operationId":"listDynwayWebhookEndpoints","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"Business Fleet webhook endpoints and quota","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpointListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}}}},"post":{"tags":["Dynway"],"summary":"Create Dynway webhook endpoint","description":"Create one Business Fleet webhook endpoint for selected Dynway event types. Targets must use HTTPS and resolve only to public addresses. The HMAC signing secret is returned exactly once, is kept in Vault, and is never stored in maindb. Requires active Dynway service and `dyn:write`.","operationId":"createDynwayWebhookEndpoint","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpointCreateRequest"}}}},"responses":{"201":{"description":"Webhook endpoint created; store the signing secret now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpointSecretResponse"}}}},"409":{"description":"Endpoint name is already in use or the Business Fleet endpoint quota is exhausted","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynWebhookEndpointQuotaExceededError"}]}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope, inactive Dynway service, or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}},"422":{"description":"Invalid endpoint name, HTTPS target, event type selection, or empty update request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unsafe_target":{"value":{"detail":"unsafe_dyn_webhook_url"}},"event_type":{"value":{"detail":"invalid_dyn_webhook_event_types"}}}}}},"503":{"description":"Vault could not store the signing secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_secret_store_unavailable"}}}}}}},"/v1/dyn/webhook-endpoints/{endpoint_id}":{"get":{"tags":["Dynway"],"summary":"Get Dynway webhook endpoint","description":"Get one customer-owned Business Fleet webhook endpoint. Signing secrets and Vault references are never returned. Requires `dyn:read`.","operationId":"getDynwayWebhookEndpoint","security":[{"AccountAuth":[]}],"parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway webhook endpoint UUID.","example":"55555555-5555-5555-5555-555555555555"}],"responses":{"200":{"description":"Webhook endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpoint"}}}},"404":{"description":"Webhook endpoint not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_endpoint_not_found"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}}}},"patch":{"tags":["Dynway"],"summary":"Update Dynway webhook endpoint","description":"Update a Business Fleet webhook endpoint without changing its signing secret. Omitted fields remain unchanged. HTTPS/public-target validation runs again when `url` changes. Requires active Dynway service and `dyn:write`.","operationId":"updateDynwayWebhookEndpoint","security":[{"AccountAuth":[]}],"parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway webhook endpoint UUID.","example":"55555555-5555-5555-5555-555555555555"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpointUpdateRequest"}}}},"responses":{"200":{"description":"Updated webhook endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpoint"}}}},"404":{"description":"Webhook endpoint not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_endpoint_not_found"}}}},"409":{"description":"Another live endpoint already uses that name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_endpoint_name_in_use"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope, inactive Dynway service, or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}},"422":{"description":"Invalid endpoint name, HTTPS target, event type selection, or empty update request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unsafe_target":{"value":{"detail":"unsafe_dyn_webhook_url"}},"event_type":{"value":{"detail":"invalid_dyn_webhook_event_types"}}}}}}}},"delete":{"tags":["Dynway"],"summary":"Delete Dynway webhook endpoint","description":"Soft-delete a Business Fleet webhook endpoint, deactivate it, and retire its signing secret from Vault after the database commit. The endpoint remains removable even when Dynway is otherwise explicitly inactive. Requires `dyn:write`.","operationId":"deleteDynwayWebhookEndpoint","security":[{"AccountAuth":[]}],"parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway webhook endpoint UUID.","example":"55555555-5555-5555-5555-555555555555"}],"responses":{"204":{"description":"Webhook endpoint deleted"},"404":{"description":"Webhook endpoint not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_endpoint_not_found"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}}}}},"/v1/dyn/webhook-endpoints/{endpoint_id}/rotate-secret":{"post":{"tags":["Dynway"],"summary":"Rotate Dynway webhook signing secret","description":"Create a replacement signing secret in Vault, atomically point the endpoint to it, then retire the prior secret. The replacement is returned exactly once. Requires active Dynway service and `dyn:write`.","operationId":"rotateDynwayWebhookEndpointSecret","security":[{"AccountAuth":[]}],"parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway webhook endpoint UUID.","example":"55555555-5555-5555-5555-555555555555"}],"responses":{"200":{"description":"Webhook secret rotated; store the replacement now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookEndpointSecretResponse"}}}},"404":{"description":"Webhook endpoint not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_endpoint_not_found"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope, inactive Dynway service, or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}},"422":{"description":"Invalid endpoint name, HTTPS target, event type selection, or empty update request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unsafe_target":{"value":{"detail":"unsafe_dyn_webhook_url"}},"event_type":{"value":{"detail":"invalid_dyn_webhook_event_types"}}}}}},"503":{"description":"Vault could not store the signing secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_secret_store_unavailable"}}}}}}},"/v1/dyn/webhook-deliveries":{"get":{"tags":["Dynway"],"summary":"List Dynway webhook deliveries","description":"List customer-owned Business Fleet webhook delivery diagnostics in descending creation order. It returns bounded response samples and sanitized errors, but never callback payloads, signing secrets, or Vault references. Requires `dyn:read`.","operationId":"listDynwayWebhookDeliveries","security":[{"AccountAuth":[]}],"parameters":[{"name":"endpoint_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Optional webhook endpoint UUID filter.","example":"55555555-5555-5555-5555-555555555555"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","delivering","delivered","failed","exhausted","cancelled"]},"description":"Optional current delivery status filter.","example":"failed"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":256},"description":"Opaque cursor from the preceding delivery response."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":100},"description":"Maximum delivery diagnostics per page.","example":50}],"responses":{"200":{"description":"Webhook delivery diagnostics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookDeliveryListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}},"422":{"description":"Invalid delivery status filter or opaque cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"status":{"value":{"detail":"invalid_dyn_webhook_delivery_status"}},"cursor":{"value":{"detail":"invalid_dyn_webhook_delivery_cursor"}}}}}}}}},"/v1/dyn/webhook-deliveries/{delivery_id}":{"get":{"tags":["Dynway"],"summary":"Get Dynway webhook delivery","description":"Get one customer-owned Business Fleet webhook delivery diagnostic without exposing its callback payload, signing secret, or Vault reference. Requires `dyn:read`.","operationId":"getDynwayWebhookDelivery","security":[{"AccountAuth":[]}],"parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway webhook delivery UUID.","example":"66666666-6666-6666-6666-666666666666"}],"responses":{"200":{"description":"Webhook delivery diagnostic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookDelivery"}}}},"404":{"description":"Webhook delivery not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_delivery_not_found"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}}}}},"/v1/dyn/webhook-deliveries/{delivery_id}/retry":{"post":{"tags":["Dynway"],"summary":"Retry Dynway webhook delivery","description":"Queue one terminal `failed` or `exhausted` Business Fleet delivery for a fresh retry cycle. The API only updates the durable ledger; the delivery worker performs the actual callback. Requires active Dynway service, an active endpoint, and `dyn:write`.","operationId":"retryDynwayWebhookDelivery","security":[{"AccountAuth":[]}],"parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway webhook delivery UUID.","example":"66666666-6666-6666-6666-666666666666"}],"responses":{"200":{"description":"Delivery queued for retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynWebhookDelivery"}}}},"404":{"description":"Webhook delivery not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_delivery_not_found"}}}},"409":{"description":"Endpoint inactive or delivery is not terminal/retryable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"endpoint_inactive":{"value":{"detail":"dyn_webhook_endpoint_inactive"}},"not_retryable":{"value":{"detail":"dyn_webhook_delivery_not_retryable"}}}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope, inactive Dynway service, or Home Free plan without Business Fleet webhooks","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}},"422":{"description":"Invalid endpoint name, HTTPS target, event type selection, or empty update request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unsafe_target":{"value":{"detail":"unsafe_dyn_webhook_url"}},"event_type":{"value":{"detail":"invalid_dyn_webhook_event_types"}}}}}},"503":{"description":"Vault could not store the signing secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_webhook_secret_store_unavailable"}}}}}}},"/v1/dyn/events":{"get":{"tags":["Dynway"],"summary":"List Dynway event history","description":"List sanitized event history owned by the authenticated WAYSCloud customer. Home Free returns at most 10 events from the last 24 hours. Business Fleet returns up to 100 events per page from the last 30 days. The optional `status` filter uses each name's current materialized status, while `event_type` filters the historical event type. Router passwords, authorization data, raw URLs, source streams, and router fingerprints are never returned.","operationId":"listDynwayEvents","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Optional Dynway label filter.","example":"oslo-gw"},{"name":"event_type","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":100},"description":"Optional exact event type, for example `dyn.update.nochg` or `dyn.name.offline`.","example":"dyn.update.nochg"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["unknown","online","late","offline","auth_error","abuse_limited","server_error","dns_drift"]},"description":"Optional current materialized status of the Dynway name. This is not a historical event result filter.","example":"offline"},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Optional timezone-aware ISO 8601 lower time bound, subject to the plan retention window.","example":"2026-08-25T12:00:00Z"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":256},"description":"Opaque cursor returned by the previous event response. Do not construct it."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":100},"description":"Requested page size. Home Free responses are capped at 10; Business Fleet responses are capped at 100.","example":50}],"responses":{"200":{"description":"Sanitized Dynway event page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynEventListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"422":{"description":"Invalid event filter, timestamp, or cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_cursor":{"value":{"detail":"invalid_dyn_event_cursor"}},"invalid_status":{"value":{"detail":"invalid_dyn_status"}}}}}}}}},"/v1/dyn/names":{"get":{"tags":["Dynway"],"summary":"List Dynway names","description":"List the customer-owned Dynway hostnames that can be updated by routers or small servers. Each name is a stable `*.dynway.eu` hostname with current IPv4/IPv6 state, materialized status, update timestamps, and active router token metadata. Business Fleet responses also include customer labels, tags, and monitoring settings. Use `status` to filter by current materialized status. Requires `dyn:read` scope; `dyn:write` implies read.","operationId":"listDynwayNames","security":[{"AccountAuth":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["unknown","online","late","offline","auth_error","abuse_limited","server_error","dns_drift"]},"description":"Optional current materialized status filter for the returned Dynway names.","example":"offline"}],"responses":{"200":{"description":"Dynway names and quota","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynNameListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}},"post":{"tags":["Dynway"],"summary":"Create Dynway name","description":"Create one customer-owned hostname under `dynway.eu` within the authenticated customer's Home Free or Business Fleet quota. Requires `dyn:write` scope.","operationId":"createDynwayName","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynNameCreateRequest"}}}},"responses":{"201":{"description":"Dynway name created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynName"}}}},"409":{"description":"Name unavailable, reserved, or plan quota exceeded","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynQuotaExceededError"}]},"examples":{"unavailable":{"value":{"detail":"dyn_name_unavailable"}},"reserved":{"value":{"detail":"reserved_dyn_name"}},"quota":{"value":{"detail":{"code":"dyn_name_quota_exceeded","plan":"business_fleet","used":50,"limit":50}}}}}}},"422":{"description":"Invalid Dynway label","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"invalid_dyn_name"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Dynway is explicitly inactive for this account","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/names/availability":{"get":{"tags":["Dynway"],"summary":"Check Dynway name availability","description":"Check whether a valid *.dynway.eu label is currently free to create. This is an advisory lookup for authenticated WAYSCloud customers: it never reveals who owns an unavailable name, and creation still enforces the live-name uniqueness constraint to handle concurrent requests. Requires dyn:read scope; dyn:write implies read.","operationId":"checkDynwayNameAvailability","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label to check, without .dynway.eu.","example":"home"}],"responses":{"200":{"description":"Availability result without owner information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynNameAvailabilityResponse"}}}},"409":{"description":"Reserved Dynway label","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"reserved_dyn_name"}}}},"422":{"description":"Invalid Dynway label","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"invalid_dyn_name"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}}}}},"/v1/dyn/names/{name}/status":{"get":{"tags":["Dynway"],"summary":"Get Dynway name status","description":"Get the customer-visible materialized status for one live Dynway hostname. Home Free receives basic address and last-seen state. Business Fleet also receives its monitoring configuration and the next expected router check-in when monitoring is active. Requires `dyn:read` scope.","operationId":"getDynwayNameStatus","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"responses":{"200":{"description":"Current Dynway status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynNameStatusResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/names/{name}/events":{"get":{"tags":["Dynway"],"summary":"List Dynway name event history","description":"List sanitized event history for one live customer-owned Dynway hostname. Home Free returns at most 10 events from the last 24 hours; Business Fleet returns up to 100 events per page from the last 30 days. Requires `dyn:read` scope.","operationId":"listDynwayNameEvents","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"},{"name":"event_type","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":100},"description":"Optional exact event type, for example `dyn.update.nochg` or `dyn.name.offline`.","example":"dyn.update.nochg"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["unknown","online","late","offline","auth_error","abuse_limited","server_error","dns_drift"]},"description":"Optional current materialized status of the Dynway name. This is not a historical event result filter.","example":"offline"},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Optional timezone-aware ISO 8601 lower time bound, subject to the plan retention window.","example":"2026-08-25T12:00:00Z"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":256},"description":"Opaque cursor returned by the previous event response. Do not construct it."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":100},"description":"Requested page size. Home Free responses are capped at 10; Business Fleet responses are capped at 100.","example":50}],"responses":{"200":{"description":"Sanitized Dynway event page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynEventListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}},"422":{"description":"Invalid event filter, timestamp, or cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_cursor":{"value":{"detail":"invalid_dyn_event_cursor"}},"invalid_status":{"value":{"detail":"invalid_dyn_status"}}}}}}}}},"/v1/dyn/names/{name}":{"get":{"tags":["Dynway"],"summary":"Get Dynway name","description":"Get current state for one dynamic DNS name, including current IP, materialized status, last update timestamps, active router token IDs, and Business Fleet metadata when applicable.","operationId":"getDynwayName","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"responses":{"200":{"description":"Dynway name details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynName"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}},"patch":{"tags":["Dynway"],"summary":"Update Business Fleet name metadata","description":"Update labels, tags, and monitoring settings for one live Dynway name. This endpoint is available only to active Business Fleet accounts and requires `dyn:write`. Omitted fields remain unchanged. `offline_after_seconds` must be greater than or equal to `expected_interval_seconds`; both values use conservative server-side bounds.","operationId":"updateDynwayNameMetadata","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynNameMetadataUpdateRequest"}}}},"responses":{"200":{"description":"Updated Dynway name and metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynName"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope, inactive Dynway service, or Home Free plan without Business Fleet metadata","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"},{"$ref":"#/components/schemas/DynwayFeatureNotAvailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}},"422":{"description":"Invalid metadata, tag, or monitoring threshold pair","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"thresholds":{"value":{"detail":"invalid_dyn_monitoring_thresholds"}},"tag":{"value":{"detail":"invalid_dyn_tag"}}}}}}}},"delete":{"tags":["Dynway"],"summary":"Delete Dynway name","description":"Soft-delete a Dynway name and revoke its active router and desktop updater credentials. External DNS record deletion is handled by the guarded cleanup process, so `dns_removal_pending` is set to true.","operationId":"deleteDynwayName","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"responses":{"200":{"description":"Dynway name soft-deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDeleteResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/names/{name}/update-source":{"get":{"tags":["Dynway"],"summary":"Get active update source","description":"Return the one active Dynway update source for a live name. A source is either a router token or a Dynway Desktop installation. The response contains customer-safe status metadata only; it never returns a credential identifier, password, or secret.","operationId":"getDynwayUpdateSource","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"responses":{"200":{"description":"Active source state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynUpdateSourceStateResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/names/{name}/desktop-agents":{"get":{"tags":["Dynway"],"summary":"List Dynway Desktop agents","description":"List current and revoked desktop updater installations for one live name. Secrets are never returned from this endpoint.","operationId":"listDynwayDesktopAgents","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"responses":{"200":{"description":"Desktop updater installations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}},"post":{"tags":["Dynway"],"summary":"Enroll Dynway Desktop","description":"Create a desktop updater credential when the name has no active update source. The secret is returned once to the enrolling client and cannot be retrieved later. If another router or desktop client is already active, use the explicit replace endpoint instead.","operationId":"enrollDynwayDesktopAgent","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentEnrollRequest"}}}},"responses":{"201":{"description":"Desktop updater enrolled; persist the secret in the local credential store now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentCredentialResponse"}}}},"409":{"description":"Another active router or desktop updater already owns this Dynway name. The response includes only the source type, display label, and last-seen timestamp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynUpdateSourceConflictError"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Dynway is explicitly inactive for this account","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}},"422":{"description":"Invalid device label, platform, version, or replacement confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"platform":{"value":{"detail":"invalid_dyn_desktop_agent_platform"}},"confirmation":{"value":{"detail":"dyn_update_source_replace_not_confirmed"}}}}}},"503":{"description":"The dataplane credential cache could not be invalidated; the mutation was not committed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_cache_invalidation_failed"}}}}}}},"/v1/dyn/names/{name}/desktop-agents/replace":{"post":{"tags":["Dynway"],"summary":"Replace active update source with Dynway Desktop","description":"Atomically revoke the active router or desktop updater and enroll this desktop installation. Set `confirm_replace` to true. The operation leaves no interval in which two updater credentials are active.","operationId":"replaceDynwayUpdateSource","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentReplaceRequest"}}}},"responses":{"200":{"description":"Source replaced; persist the new desktop secret in the local credential store now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentCredentialResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Dynway is explicitly inactive for this account","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}},"422":{"description":"Invalid device label, platform, version, or replacement confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"platform":{"value":{"detail":"invalid_dyn_desktop_agent_platform"}},"confirmation":{"value":{"detail":"dyn_update_source_replace_not_confirmed"}}}}}},"503":{"description":"The dataplane credential cache could not be invalidated; the mutation was not committed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_cache_invalidation_failed"}}}}}}},"/v1/dyn/names/{name}/desktop-agents/{agent_id}/rotate":{"post":{"tags":["Dynway"],"summary":"Rotate Dynway Desktop credential","description":"Replace the credential for the active desktop updater and return the new secret once. The previous credential is invalidated synchronously before the mutation commits.","operationId":"rotateDynwayDesktopAgent","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway Desktop agent UUID.","example":"55555555-5555-5555-5555-555555555555"}],"responses":{"200":{"description":"Desktop credential rotated; persist the secret in the local credential store now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentCredentialResponse"}}}},"409":{"description":"The agent is no longer the active source, or another source is active.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynUpdateSourceConflictError"}]}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Dynway is explicitly inactive for this account","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}},"422":{"description":"Invalid device label, platform, version, or replacement confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"platform":{"value":{"detail":"invalid_dyn_desktop_agent_platform"}},"confirmation":{"value":{"detail":"dyn_update_source_replace_not_confirmed"}}}}}},"503":{"description":"The dataplane credential cache could not be invalidated; the mutation was not committed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_cache_invalidation_failed"}}}}}}},"/v1/dyn/names/{name}/desktop-agents/{agent_id}":{"delete":{"tags":["Dynway"],"summary":"Revoke Dynway Desktop agent","description":"Immediately revoke a desktop updater credential. When the agent is the active source, the name is left without an active updater until a router or desktop client is enrolled explicitly.","operationId":"revokeDynwayDesktopAgent","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"},{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer-owned Dynway Desktop agent UUID.","example":"55555555-5555-5555-5555-555555555555"}],"responses":{"200":{"description":"Desktop updater revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynDesktopAgentRevokeResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}},"503":{"description":"The dataplane credential cache could not be invalidated; the revocation was not committed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_cache_invalidation_failed"}}}}}}},"/v1/dyn/names/{name}/tokens":{"get":{"tags":["Dynway"],"summary":"List router tokens","description":"List router token metadata for a Dynway name. Token passwords are never returned after creation.","operationId":"listDynwayTokens","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"responses":{"200":{"description":"Router token metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynTokenListResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}},"post":{"tags":["Dynway"],"summary":"Create router token","description":"Issue the first active router token for a Dynway name. The router password is returned once and cannot be retrieved later. A live desktop source blocks this operation until it is explicitly replaced or revoked.","operationId":"createDynwayToken","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynTokenRequest"}}}},"responses":{"201":{"description":"Router token created; save the password now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynTokenResponse"}}}},"409":{"description":"An active router token already exists or an active desktop source owns the name.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynUpdateSourceConflictError"}]},"examples":{"router_token":{"value":{"detail":"active_dyn_token_exists"}},"desktop_source":{"value":{"detail":{"code":"dyn_update_source_conflict","active_source":{"source_type":"desktop","display_label":"Office Mac","last_seen_at":"2026-08-26T10:15:30Z"}}}}}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Dynway is explicitly inactive for this account","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/names/{name}/tokens/rotate":{"post":{"tags":["Dynway"],"summary":"Rotate router token","description":"Revoke active router tokens for the Dynway name and return one new token. Update the router immediately with the returned username/password. A live desktop source blocks this operation until it is explicitly replaced or revoked.","operationId":"rotateDynwayToken","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynTokenRequest"}}}},"responses":{"200":{"description":"Router token rotated; save the new password now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynTokenResponse"}}}},"409":{"description":"Another active router or desktop updater already owns this Dynway name. The response includes only the source type, display label, and last-seen timestamp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynUpdateSourceConflictError"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope or Dynway is explicitly inactive for this account","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/DynwayUnavailableError"}]}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/v1/dyn/names/{name}/tokens/{token_id}":{"delete":{"tags":["Dynway"],"summary":"Revoke router token","description":"Revoke one active router token and invalidate the dataplane warm cache.","operationId":"revokeDynwayToken","security":[{"AccountAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"description":"Dynway label without `.dynway.eu`, for example `home`.","example":"home"},{"name":"token_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":50},"description":"Router token ID.","example":"dyn7f3a91c4"}],"responses":{"200":{"description":"Router token revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DynTokenRevokeResponse"}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unauthorized"}}}},"403":{"description":"Missing required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient permissions"}}}},"404":{"description":"Dynway name or token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"dyn_name_not_found"}}}}}}},"/nic/update":{"get":{"tags":["Dynway"],"summary":"DynDNS2 router update","description":"Update the A or AAAA record for a Dynway hostname from a router or small server.\n\nThis endpoint is served from `https://dynupdate.wayscloud.services`, not from the main API host. Use HTTP Basic Auth with the Dynway token ID as username and the router password as password.\n\nDynway accepts public IPv4 and IPv6 addresses. IPv4 updates maintain the A record; IPv6 updates maintain the AAAA record. Dynway follows DynDNS2 response semantics: protocol success and protocol errors are returned as HTTP `200`, and the response body contains values such as `good <ip>`, `nochg <ip>`, `badauth`, `nohost`, `notfqdn`, `badagent`, `abuse`, or `911`.","operationId":"updateDynwayAddress","servers":[{"url":"https://dynupdate.wayscloud.services","description":"Dynway update endpoint"}],"security":[{"DynDnsBasicAuth":[]}],"parameters":[{"name":"hostname","in":"query","required":true,"schema":{"type":"string"},"description":"Full Dynway hostname.","example":"home.dynway.eu"},{"name":"myip","in":"query","required":false,"schema":{"type":"string"},"description":"Optional public IPv4 or IPv6 address. If omitted or invalid, Dynway uses the address seen by nginx and updates that address family.","examples":{"ipv4":{"value":"203.0.113.10"},"ipv6":{"value":"2001:4860:4860::8888"}}}],"responses":{"200":{"description":"DynDNS2 protocol response","content":{"text/plain":{"schema":{"type":"string"},"examples":{"changed":{"value":"good 203.0.113.10"},"unchanged":{"value":"nochg 203.0.113.10"},"changed_ipv6":{"value":"good 2001:4860:4860::8888"},"auth_failed":{"value":"badauth"},"backend_error":{"value":"911"}}}}}}}},"/v1/regions":{"get":{"tags":["Regions"],"summary":"List regions","description":"Get available regions with service availability (storage, database, VPS, LLM).","operationId":"listRegions","parameters":[{"name":"service","in":"query","schema":{"type":"string","enum":["storage","database","llm","vps"]},"description":"Filter by service"},{"name":"status","in":"query","schema":{"type":"string","enum":["active","maintenance","deprecated"]},"description":"Filter by status"}],"responses":{"200":{"description":"Regions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionList"}}}}}}},"/v1/regions/{region_code}":{"get":{"tags":["Regions"],"summary":"Get region","description":"Get region details: available services, location, and pricing.","operationId":"getRegion","parameters":[{"name":"region_code","in":"path","required":true,"schema":{"type":"string"},"description":"Region code (e.g., no, se, de)"}],"responses":{"200":{"description":"Region details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Region"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}}},"/v1/regions/{region_code}/pricing":{"get":{"tags":["Regions"],"summary":"Get pricing","description":"Get service prices for a specific region.","operationId":"getRegionPricing","parameters":[{"name":"region_code","in":"path","required":true,"schema":{"type":"string"},"description":"Region code"}],"responses":{"200":{"description":"Service pricing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionPricing"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}}},"/v1/llm/models":{"get":{"tags":["LLM"],"summary":"List models","description":"List available models (WAYSCloud endpoint)","operationId":"list_llm_models_v1_llm_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIModelList"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/models":{"get":{"tags":["LLM"],"summary":"List models","description":"List available models (OpenAI-compatible endpoint)\n\nReturns a list of all available LLM models.\n\n**Response Example:**\n```json\n{\n  \"object\": \"list\",\n  \"data\": [\n    {\"id\": \"deepseek-v3\", \"object\": \"model\", \"owned_by\": \"wayscloud\"},\n    {\"id\": \"qwen3-235b-instruct\", \"object\": \"model\", \"owned_by\": \"wayscloud\"},\n    {\"id\": \"qwen3-vl-8b\", \"object\": \"model\", \"owned_by\": \"wayscloud\"}\n  ]\n}\n```","operationId":"list_openai_models_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIModelList"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/llm/chat":{"post":{"tags":["LLM"],"summary":"Chat completion","description":"WAYSCloud LLM chat completion endpoint\n\nSupports both streaming and non-streaming responses.","operationId":"llm_chat_completion_v1_llm_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatResponse"},{"$ref":"#/components/schemas/ErrorResponse"}],"title":"Response Llm Chat Completion V1 Llm Chat Post"},"example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"qwen3-235b-thinking","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"error":{"message":"messages is required and must be a non-empty array","type":"invalid_request_error","code":"validation_error"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"error":{"message":"messages is required","type":"invalid_request_error","code":"missing_field"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"example":{"error":{"message":"Invalid API key","type":"authentication_error","code":"invalid_api_key"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"message":"Rate limit exceeded. Retry after 60 seconds.","type":"rate_limit_error","code":"rate_limit_exceeded"}}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/llm/chat/completions":{"post":{"tags":["LLM"],"summary":"Chat completion","description":"WAYSCloud LLM chat completion endpoint (with /completions suffix)\n\nAlias for /v1/llm/chat for nginx compatibility.\nSupports both streaming and non-streaming responses.","operationId":"llm_chat_completions_v1_llm_chat_completions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatResponse"},{"$ref":"#/components/schemas/ErrorResponse"}],"title":"Response Llm Chat Completions V1 Llm Chat Completions Post"},"example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"qwen3-235b-thinking","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"error":{"message":"messages is required and must be a non-empty array","type":"invalid_request_error","code":"validation_error"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"error":{"message":"messages is required","type":"invalid_request_error","code":"missing_field"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"example":{"error":{"message":"Invalid API key","type":"authentication_error","code":"invalid_api_key"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"message":"Rate limit exceeded. Retry after 60 seconds.","type":"rate_limit_error","code":"rate_limit_exceeded"}}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/chat/completions":{"post":{"tags":["LLM"],"summary":"Chat completion","description":"OpenAI-compatible chat completion endpoint\n\nDrop-in replacement for OpenAI's `/v1/chat/completions` endpoint.\nCompatible with OpenAI SDK and all OpenAI-compatible clients.\n\n**Features:**\n- Non-streaming and streaming (SSE) responses\n- Temperature and max_tokens control\n- Agents framework support (tools, agent_id, tool_choice)\n- Automatic token counting and billing\n\n**Request Example:**\n```json\n{\n  \"model\": \"mixtral-8x7b\",\n  \"messages\": [\n    {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n    {\"role\": \"user\", \"content\": \"Hello!\"}\n  ],\n  \"temperature\": 0.7,\n  \"max_tokens\": 100\n}\n```\n\n**Streaming Example:**\n```json\n{\n  \"model\": \"deepseek-v3\",\n  \"messages\": [{\"role\": \"user\", \"content\": \"Write a story\"}],\n  \"stream\": true,\n  \"max_tokens\": 500\n}\n```\n\n**AI Agent Example:**\n```json\n{\n  \"model\": \"deepseek-v3\",\n  \"messages\": [{\"role\": \"user\", \"content\": \"Help me code\"}],\n  \"agent_id\": \"ephemeral\",\n  \"tools\": [],\n  \"tool_choice\": \"auto\"\n}\n```\n\n**Available Models:**\n\n*Chat — general purpose:*\n- `deepseek-v3`, `deepseek-v3.1` - Versatile, 131k context\n- `qwen3-235b-instruct` - Flagship MoE, 262k context\n- `llama-3.3-70b` - Enterprise, 131k context\n- `mixtral-8x7b` - Fast lightweight MoE\n- `gpt-oss-120b`, `gpt-oss-20b` - Open-weight\n- `gemma-3n-e4b` - Ultra-lightweight, cheapest\n- `kimi-k2.5` - Long-context conversational\n- `qwen3.5-9b` - Lightweight, 262k context\n\n*Reasoning:*\n- `deepseek-r1` - Advanced reasoning\n- `qwen3-235b-thinking` - MoE reasoning\n\n*Code:*\n- `qwen3-coder-480b` - Code generation\n\n*Vision (multimodal):*\n- `qwen3-vl-8b` - Text + image input\n\n*Embeddings:*\n- `embedding-multilingual` - 1024-dim, for RAG\n\n*Moderation:*\n- `llamaguard-4` - Content safety","operationId":"openai_chat_completion_v1_chat_completions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatResponse"},{"$ref":"#/components/schemas/ErrorResponse"}],"title":"Response Openai Chat Completion V1 Chat Completions Post"},"example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"qwen3-235b-thinking","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"error":{"message":"messages is required and must be a non-empty array","type":"invalid_request_error","code":"validation_error"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"error":{"message":"messages is required","type":"invalid_request_error","code":"missing_field"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"example":{"error":{"message":"Invalid API key","type":"authentication_error","code":"invalid_api_key"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"message":"Rate limit exceeded. Retry after 60 seconds.","type":"rate_limit_error","code":"rate_limit_exceeded"}}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/llm/embeddings":{"post":{"tags":["LLM"],"summary":"Create Embedding","description":"OpenAI-compatible embeddings endpoint.\n\nRequest body:\n    {\"model\": \"embedding-multilingual\", \"input\": \"text\" | [\"t1\", \"t2\"]}\n\nResponse: OpenAI-compatible embedding list.","operationId":"create_embedding_v1_llm_embeddings_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/embeddings":{"post":{"tags":["LLM"],"summary":"Create Embedding","description":"OpenAI-compatible embeddings endpoint.\n\nRequest body:\n    {\"model\": \"embedding-multilingual\", \"input\": \"text\" | [\"t1\", \"t2\"]}\n\nResponse: OpenAI-compatible embedding list.","operationId":"create_embedding_v1_embeddings_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"ServiceAuth":[]}]}},"/storage/s3":{"get":{"tags":["Storage"],"summary":"S3 protocol guide","description":"WAYSCloud Storage is fully AWS S3 compatible. Use any AWS SDK or S3 client.\n\n**Endpoint:** `https://storage.wayscloud.services` | **Region:** `eu-north-1`\n\n**AWS CLI**\n\n```bash\n# Configure\naws configure set default.s3.endpoint_url https://storage.wayscloud.services\n\n# List buckets\naws s3 ls\n\n# Upload file\naws s3 cp local.txt s3://your-bucket/remote.txt\n\n# Download file\naws s3 cp s3://your-bucket/remote.txt local.txt\n```\n\n**Python (boto3)**\n\n```python\nimport boto3\n\ns3 = boto3.client('s3',\n    endpoint_url='https://storage.wayscloud.services',\n    aws_access_key_id='YOUR_ACCESS_KEY',\n    aws_secret_access_key='YOUR_SECRET_KEY',\n    region_name='eu-north-1'\n)\n\n# List objects\nresponse = s3.list_objects_v2(Bucket='your-bucket')\nfor obj in response.get('Contents', []):\n    print(obj['Key'])\n\n# Upload file\ns3.upload_file('local.txt', 'your-bucket', 'remote.txt')\n\n# Download file\ns3.download_file('your-bucket', 'remote.txt', 'local.txt')\n```\n\n**JavaScript (AWS SDK v3)**\n\n```javascript\nimport { S3Client, ListObjectsV2Command } from \"@aws-sdk/client-s3\";\n\nconst s3 = new S3Client({\n  endpoint: \"https://storage.wayscloud.services\",\n  region: \"eu-north-1\",\n  credentials: {\n    accessKeyId: \"YOUR_ACCESS_KEY\",\n    secretAccessKey: \"YOUR_SECRET_KEY\"\n  }\n});\n\nconst response = await s3.send(new ListObjectsV2Command({\n  Bucket: \"your-bucket\"\n}));\n```\n\nGet your Access Key and Secret Key from `/v1/storage/credentials` or your [dashboard](https://my.wayscloud.services).\n","operationId":"storageS3Info","responses":{"200":{"description":"S3 protocol documentation"}}}},"/v1/domain-verification/domains":{"post":{"tags":["Domain Verification"],"summary":"Register domain","description":"Register a domain for verification. Returns DNS setup instructions.\n\nAfter registration, add the DNS record to your domain to prove ownership. Then call the verify endpoint to check.\n\n**Purposes:**\n- `email` - For sending email from this domain\n- `custom_host` - For custom hostnames/CNAMEs\n- `webhook` - For webhook endpoint verification\n- `link_branding` - For branded tracking links\n- `verify_channel` - For communication channel verification\n- `other` - General domain ownership verification\n","operationId":"registerDomain","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDomainRequest"}}}},"responses":{"200":{"description":"Domain registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"400":{"description":"Invalid domain or already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid domain format or domain already registered"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token (requires scope: domain_verification:write)"}}}}}},"get":{"tags":["Domain Verification"],"summary":"List domains","description":"List all domain verifications for your account. Supports filtering by purpose and status.","operationId":"listDomains","security":[{"AccountAuth":[]}],"parameters":[{"name":"purpose","in":"query","schema":{"type":"string","enum":["email","custom_host","webhook","link_branding","verify_channel","other"]},"description":"Filter by purpose"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","verified","failed","revoked"]},"description":"Filter by status"},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"page_size","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100},"description":"Items per page"}],"responses":{"200":{"description":"Domain list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerificationList"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token (requires scope: domain_verification:read)"}}}}}}},"/v1/domain-verification/domains/{domain_id}":{"get":{"tags":["Domain Verification"],"summary":"Get domain details","description":"Get details for a specific domain verification including DNS instructions.","operationId":"getDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Domain details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}},"patch":{"tags":["Domain Verification"],"summary":"Revoke domain","description":"Revoke a verified domain. Only status change to `revoked` is allowed.","operationId":"revokeDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["revoked"]}}},"example":{"status":"revoked"}}}},"responses":{"200":{"description":"Domain revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"400":{"description":"Invalid status change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid status change for this domain"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}},"delete":{"tags":["Domain Verification"],"summary":"Delete domain","description":"Delete a domain verification. This is a soft delete - records are preserved for audit.","operationId":"deleteDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Domain deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}}},"/v1/domain-verification/domains/{domain_id}/verify":{"post":{"tags":["Domain Verification"],"summary":"Verify domain","description":"Trigger a DNS verification check for the domain.\n\nPerforms DNS lookup to verify that the required record exists. Returns the verification result immediately.\n\n**DNS record must be in place before calling this endpoint.**\n","operationId":"verifyDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyDomainResponse"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}}},"/v1/domain-verification/domains/{domain_id}/history":{"get":{"tags":["Domain Verification"],"summary":"Get verification history","description":"Get the verification history for a domain including all check attempts and status changes.","operationId":"getDomainHistory","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Verification history","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DomainVerificationHistory"},"example":[{"id":"c3d4e5f6-a7b8-9012-cdef-345678901234","event_type":"check_completed","previous_status":"pending","new_status":"verified","dns_response":{"records_found":true,"actual_values":["wayscloud-domain-verification=abc123xyz789"]},"performed_by":"api","created_at":"2025-12-08T10:30:00Z"}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}}},"/v1/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"List all contacts with optional filtering by type, search query, or tags.","operationId":"listContacts","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Search by name, email, or phone"},{"name":"type","in":"query","schema":{"type":"string","enum":["person","company"]},"description":"Filter by contact type"},{"name":"tags","in":"query","schema":{"type":"string"},"description":"Filter by tag (comma-separated for multiple)"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":50,"maximum":100}}],"responses":{"200":{"description":"Contact list","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"}},"example":{"contacts":[{"id":"a1b2c3d4-5678-9abc-def0-123456789abc","first_name":"Ola","last_name":"Nordmann","email":"ola@example.com","phone_number":"+4799999999","contact_type":"person","tags":["vip"]}],"total":1,"page":1,"per_page":50}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}},"post":{"tags":["Contacts"],"summary":"Create contact","description":"Create a new contact. Phone numbers should be in E.164 format for SMS sending.","operationId":"createContact","security":[{"AccountAuth":["contacts:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"201":{"description":"Contact created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid input. Check required fields."}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}}},"/v1/contacts/{contact_id}":{"get":{"tags":["Contacts"],"summary":"Get contact","description":"Get details of a specific contact.","operationId":"getContact","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}},"patch":{"tags":["Contacts"],"summary":"Update contact","description":"Update an existing contact. Only provided fields are updated.","operationId":"updateContact","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"Contact updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}},"delete":{"tags":["Contacts"],"summary":"Delete contact","description":"Delete a contact. This is a soft delete - the contact can be restored via GDPR endpoints.","operationId":"deleteContact","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Contact deleted"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}}},"/v1/contacts/{contact_id}/gdpr-delete":{"post":{"tags":["Contacts"],"summary":"GDPR anonymize contact","description":"Permanently anonymize contact data for GDPR compliance. This is irreversible.","operationId":"gdprDeleteContact","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Reason for GDPR deletion"}}},"example":{"reason":"Customer requested data deletion per GDPR Article 17"}}}},"responses":{"200":{"description":"Contact anonymized"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}}},"/v1/contacts/groups":{"get":{"tags":["Contacts"],"summary":"List contact groups","description":"List all contact groups with member counts.","operationId":"listContactGroups","security":[{"AccountAuth":["contacts:read"]}],"responses":{"200":{"description":"Group list","content":{"application/json":{"schema":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/ContactGroup"}}},"example":{"groups":[{"id":"a1b2c3d4-1234-5678-9abc-def012345678","name":"Newsletter Subscribers","member_count":150,"created_at":"2025-01-15T09:30:00Z"}]}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}},"post":{"tags":["Contacts"],"summary":"Create contact group","description":"Create a new contact group for organizing contacts.","operationId":"createContactGroup","security":[{"AccountAuth":["contacts:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupCreate"}}}},"responses":{"201":{"description":"Group created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid input. Check required fields."}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}}},"/v1/contacts/groups/{group_id}":{"get":{"tags":["Contacts"],"summary":"Get contact group","description":"Get details of a specific contact group including member list.","operationId":"getContactGroup","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Group details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}},"patch":{"tags":["Contacts"],"summary":"Update contact group","description":"Update group name or description.","operationId":"updateContactGroup","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupCreate"}}}},"responses":{"200":{"description":"Group updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}},"delete":{"tags":["Contacts"],"summary":"Delete contact group","description":"Delete a contact group. Contacts in the group are not deleted.","operationId":"deleteContactGroup","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Group deleted"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}}},"/v1/contacts/groups/{group_id}/members":{"get":{"tags":["Contacts"],"summary":"List group members","description":"List all contacts in a group.","operationId":"listGroupMembers","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Member list","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}},"example":{"members":[{"id":"b2c3d4e5-6789-0abc-def1-234567890abc","first_name":"Ola","last_name":"Nordmann","phone_number":"+4799999999"}]}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}},"post":{"tags":["Contacts"],"summary":"Add members to group","description":"Add one or more contacts to a group.","operationId":"addGroupMembers","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_ids"],"properties":{"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}},"example":{"contact_ids":["a1b2c3d4-5678-9abc-def0-123456789abc","b2c3d4e5-6789-0abc-def1-234567890abc"]}}}},"responses":{"200":{"description":"Members added","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"}},"example":{"added":3}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}}},"/v1/contacts/groups/{group_id}/members/{contact_id}":{"delete":{"tags":["Contacts"],"summary":"Remove member from group","description":"Remove a contact from a group.","operationId":"removeGroupMember","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Member removed"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group or contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group or contact not found"}}}}}}},"/v1/verify/start":{"post":{"tags":["Verify"],"summary":"Start verification","description":"Start a new verification session and send code via SMS, voice call, or email.\n\n## Example Request (SMS)\n```json\n{\n  \"channel\": \"sms\",\n  \"recipient\": \"+4712345678\",\n  \"code_length\": 6,\n  \"ttl_seconds\": 300,\n  \"client_reference\": \"signup-user-42\",\n  \"locale\": \"no\"\n}\n```\n\n## Example Request (Voice)\n```json\n{\n  \"channel\": \"voice\",\n  \"recipient\": \"+4712345678\",\n  \"locale\": \"no\"\n}\n```\n\n## Example Request (Email)\n```json\n{\n  \"channel\": \"email\",\n  \"recipient\": \"user@example.com\",\n  \"locale\": \"no\"\n}\n```\n\n## Parameters\n| Field | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `channel` | No | `sms` | Channel: `sms`, `voice`, or `email` |\n| `recipient` | **Yes** | - | Phone (E.164) for SMS/Voice, email for Email |\n| `code_length` | No | `6` | Code length (4-8 digits) |\n| `ttl_seconds` | No | `300` | Session TTL in seconds (60-900) |\n| `client_reference` | No | - | Your reference for tracking |\n| `locale` | No | `en` | Message language: en, no, nb, sv, da, fi |\n| `metadata` | No | - | Custom metadata object (stored, not processed) |\n| `client_ip` | No | - | End-user IP for risk scoring. If provided, `ip_risk` is included in response |\n\n## Channel Notes\n\n**SMS**: Requires sender name configured in dashboard settings.\n\n**Voice**: Caller ID +47 21 53 50 30. Code read twice. Languages: en, no, sv, da, fi.\n\n**Email**: From-address is your verified domain or `no-reply@wayscloud.net`. Verify domain via Domain Verification API to use custom from-address.\n","operationId":"startVerification","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipient"],"properties":{"channel":{"type":"string","enum":["sms","voice","email"],"default":"sms","description":"Verification channel"},"recipient":{"type":"string","description":"Phone (E.164) or email address","example":"+4712345678"},"code_length":{"type":"integer","minimum":4,"maximum":8,"default":6,"description":"Verification code length"},"ttl_seconds":{"type":"integer","minimum":60,"maximum":900,"default":300,"description":"Session TTL in seconds"},"client_reference":{"type":"string","description":"Your tracking reference (e.g., signup-123)"},"locale":{"type":"string","enum":["en","no","nb","sv","da","fi"],"default":"en","description":"Message language"},"metadata":{"type":"object","description":"Custom metadata (stored, not processed)"},"client_ip":{"type":"string","description":"End-user IP for risk scoring. If provided, ip_risk is included in response","example":"203.0.113.42"}}},"examples":{"sms":{"summary":"SMS verification","value":{"channel":"sms","recipient":"+4712345678","locale":"no"}},"voice":{"summary":"Voice verification (TTS call)","value":{"channel":"voice","recipient":"+4712345678","locale":"no"}},"email":{"summary":"Email verification","value":{"channel":"email","recipient":"user@example.com","locale":"no"}}}}}},"responses":{"201":{"description":"Verification started","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","delivered"]},"channel":{"type":"string","enum":["sms","voice","email"]},"expires_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"ip_risk":{"type":"object","nullable":true,"description":"IP risk assessment (only if client_ip provided)","properties":{"score":{"type":"number","description":"Threat score 0-100"},"is_vpn":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"total_reports":{"type":"integer"},"country_code":{"type":"string","nullable":true},"error":{"type":"string","nullable":true,"description":"Error if lookup failed"}}}}},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","status":"delivered","channel":"sms","expires_at":"2025-12-12T10:05:00Z","created_at":"2025-12-12T10:00:00Z"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid recipient format. Use E.164 for SMS/voice or email address."}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"503":{"description":"Channel temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Voice channel temporarily unavailable. Try SMS instead."}}}}}}},"/v1/verify/check":{"post":{"tags":["Verify"],"summary":"Check verification code","description":"Verify the code entered by the user.\n\n## Example Request\n```json\n{\n  \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"code\": \"123456\",\n  \"client_ip\": \"203.0.113.42\"\n}\n```\n\nThe `client_ip` is optional. If provided, the response includes `ip_risk` with threat assessment data.\n\n## Error Codes\n| Error | HTTP | Meaning | User Action |\n|-------|------|---------|-------------|\n| `invalid_code` | 200 | Wrong code entered | Show remaining attempts |\n| `session_expired` | 200 | Session TTL exceeded | Start new verification |\n| `max_attempts_exceeded` | 200 | 5 attempts used | Start new verification |\n| `session_not_found` | 404 | Invalid session ID | Check session_id |\n| `voice_no_answer` | 200 | Voice call not answered | Try SMS or retry |\n| `voice_busy` | 200 | Phone line busy | Retry later |\n\nSessions are automatically locked after 5 failed attempts.\n","operationId":"checkVerification","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["session_id","code"],"properties":{"session_id":{"type":"string","format":"uuid","description":"Session ID from /start response"},"code":{"type":"string","minLength":4,"maxLength":8,"description":"Verification code entered by user"},"client_ip":{"type":"string","description":"End-user IP for risk scoring","example":"203.0.113.42"}}},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","code":"123456"}}}},"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["verified","pending","failed","expired"]},"verified_at":{"type":"string","format":"date-time"},"error":{"type":"string","description":"Error code if failed"},"attempts_remaining":{"type":"integer","description":"Remaining attempts"},"ip_risk":{"type":"object","nullable":true,"description":"IP risk assessment (only if client_ip provided)","properties":{"score":{"type":"number","description":"Threat score 0-100"},"is_vpn":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"total_reports":{"type":"integer"},"country_code":{"type":"string","nullable":true},"error":{"type":"string","nullable":true}}}}},"examples":{"success":{"summary":"Successful verification","value":{"status":"verified","verified_at":"2025-12-12T10:01:23Z"}},"invalid_code":{"summary":"Wrong code","value":{"status":"pending","error":"invalid_code","attempts_remaining":4}},"expired":{"summary":"Session expired","value":{"status":"expired","error":"session_expired"}},"max_attempts":{"summary":"Too many attempts","value":{"status":"failed","error":"max_attempts_exceeded","attempts_remaining":0}}}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Session not found"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/verify/status/{session_id}":{"get":{"tags":["Verify"],"summary":"Get session status","description":"Get current status of a verification session.\n\n## Response Fields\n| Field | Description |\n|-------|-------------|\n| `session_id` | Unique session identifier |\n| `channel` | Channel used: `sms` or `voice` |\n| `status` | Current status |\n| `recipient` | Full phone number |\n| `attempt_count` | Number of /check attempts made |\n| `max_attempts` | Maximum allowed attempts (5) |\n| `client_reference` | Your tracking reference |\n\n## Status Values\n- `pending` - Session created, delivery in progress\n- `delivered` - Code sent/played to recipient\n- `verified` - Successfully verified ✓\n- `failed` - Max attempts or delivery failed\n- `expired` - Session TTL exceeded\n- `cancelled` - Manually cancelled\n","operationId":"getVerificationStatus","security":[{"BearerAuth":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID from /start response"}],"responses":{"200":{"description":"Session status","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"channel":{"type":"string","enum":["sms","voice","email"]},"status":{"type":"string","enum":["pending","delivered","verified","failed","expired","cancelled"]},"recipient":{"type":"string","example":"+4712345678"},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"delivered_at":{"type":"string","format":"date-time"},"verified_at":{"type":"string","format":"date-time"},"attempt_count":{"type":"integer"},"max_attempts":{"type":"integer","default":5},"client_reference":{"type":"string"},"error_message":{"type":"string","description":"Error details if failed"}}},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","channel":"sms","status":"delivered","recipient":"+4712345678","created_at":"2025-12-12T10:00:00Z","expires_at":"2025-12-12T10:05:00Z","delivered_at":"2025-12-12T10:00:01Z","verified_at":null,"attempt_count":0,"max_attempts":5,"client_reference":"signup-user-42"}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Session not found"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/verify/sessions/{session_id}":{"delete":{"tags":["Verify"],"summary":"Cancel session","description":"Cancel a pending or delivered verification session. Cannot cancel verified, failed, or expired sessions.","operationId":"cancelVerificationSession","security":[{"BearerAuth":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Session cancelled"},"404":{"description":"Session not found or cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Session not found or already completed"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/gpu/engines":{"get":{"tags":["GPU Studio"],"summary":"List engines","description":"List available GPU engines for image and video generation. No authentication required.","operationId":"listGpuEngines","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["image","video"]},"description":"Filter by type"}],"responses":{"200":{"description":"List of engines","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUEngineList"}}}}}}},"/v1/gpu/presets":{"get":{"tags":["GPU Studio"],"summary":"List presets","description":"List available style presets. No authentication required.","operationId":"listGpuPresets","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["image","video"]},"description":"Filter by type"}],"responses":{"200":{"description":"List of presets","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GPUPreset"},"example":[{"name":"cinematic_light","type":"image","description":"Cinematic lighting style"}]}}}}}}},"/v1/gpu/pricing":{"get":{"tags":["GPU Studio"],"summary":"Get pricing","description":"Returns the current public price list per engine. Availability for your account is determined by GET /v1/gpu/engines. No authentication required.","operationId":"getGpuPricing","parameters":[{"name":"currency","in":"query","schema":{"type":"string","enum":["NOK","EUR","USD","SEK","DKK"],"default":"NOK"},"description":"Currency"}],"responses":{"200":{"description":"Pricing list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GPUPricing"},"example":[{"engine":"image-flux-fast","sku":"gpu_image_flux_fast","type":"image","unit":"image","price_per_unit":2.0,"currency":"NOK","display_name":"FLUX Fast","description":"Quick image generation","tier_required":null}]}}}}}}},"/v1/gpu/jobs":{"post":{"tags":["GPU Studio"],"summary":"Create job","description":"Create an image or video generation job.\n\n**Image inputs:**\n- `prompt` (required): Image description\n- `aspect_ratio`: 1:1, 4:5, 16:9, 9:16 (default: 16:9)\n- `negative_prompt`: What to avoid\n- `seed`: Random seed for reproducibility\n\n**Video inputs:**\n- `prompt` (required): Video description\n- `duration_sec`: Duration in seconds (engine-limited)\n- `negative_prompt`: What to avoid\n- `seed`: Random seed for reproducibility","operationId":"createGpuJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUJobCreate"}}}},"responses":{"201":{"description":"Job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUJob"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid engine. See GET /v1/gpu/engines for available options."}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"403":{"description":"Tier access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"This engine requires pro tier. Upgrade at my.wayscloud.services"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Rate limit exceeded. Retry after 60 seconds."}}}},"451":{"description":"Content blocked by moderation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Content blocked by safety filters. Revise your prompt."}}}},"502":{"description":"Generation service temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Generation service temporarily unavailable. Retry later."}}}}}},"get":{"tags":["GPU Studio"],"summary":"List jobs","description":"List jobs for your account.","operationId":"listGpuJobs","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]},"description":"Filter by status"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Max results"},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Pagination offset"}],"responses":{"200":{"description":"Job list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GPUJob"},"example":[{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","type":"image","engine":"image-flux-fast","status":"completed","progress":100,"output_files":{"image":"https://storage.wayscloud.services/gpu-studio/tenant/<tenant_id>/jobs/<job_id>/output.png"},"estimated_cost":{"amount":2.0,"currency":"NOK"},"actual_cost":{"amount":2.0,"currency":"NOK"},"error_message":null,"created_at":"2025-01-15T10:30:00Z","completed_at":"2025-01-15T10:30:12Z"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/gpu/jobs/{job_id}":{"get":{"tags":["GPU Studio"],"summary":"Get job","description":"Get job status and details.","operationId":"getGpuJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Job details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUJob"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}}}},"delete":{"tags":["GPU Studio"],"summary":"Cancel job","description":"Cancel a job if possible. Only queued or processing jobs can be cancelled.","operationId":"cancelGpuJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"204":{"description":"Job cancelled"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}},"409":{"description":"Job cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job cannot be cancelled (already completed)"}}}}}}},"/v1/transcript/jobs":{"post":{"tags":["Speech Intelligence"],"summary":"Create transcription job","description":"Create a transcription job and receive a presigned upload URL.\n\nThe returned `upload_url` is a presigned S3 PUT URL valid for 1 hour. Upload your file directly to this URL, then call the upload-complete endpoint to start processing.\n\nIf the file is not uploaded within 1 hour, the job expires automatically (status becomes `expired`).\n\n**Content validation:** The server validates the file using ffprobe after upload — the file must contain at least one audio stream. The `content_type` field is metadata only; actual format detection is server-side.","operationId":"createTranscriptJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptJobCreate"}}}},"responses":{"200":{"description":"Job created with upload URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptJobCreateResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unsupported audio format. Supported: mp3, wav, m4a, ogg, flac"}}}}}},"get":{"tags":["Speech Intelligence"],"summary":"List transcription jobs","description":"List transcription jobs for your account with optional status filter.","operationId":"listTranscriptJobs","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["created","queued","processing","ready","failed","expired"]},"description":"Filter by status"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Max results"},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Pagination offset"}],"responses":{"200":{"description":"Job list","content":{"application/json":{"schema":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptJob"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"example":{"jobs":[{"job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","status":"ready","language":"no","original_filename":"meeting.mp3","audio_duration_sec":1847.3,"created_at":"2026-03-30T10:00:00Z"}],"total":1,"limit":20,"offset":0}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/transcript/jobs/{job_id}":{"get":{"tags":["Speech Intelligence"],"summary":"Get transcription job","description":"Get full job details. When status is `ready`, the response includes transcription segments and available export artifacts with presigned download URLs.","operationId":"getTranscriptJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Job details with segments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptJob"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}}}},"delete":{"tags":["Speech Intelligence"],"summary":"Delete transcription job","description":"Delete a transcription job. The job is marked as deleted and S3 artifacts are cleaned up. The job will no longer appear in list results.","operationId":"deleteTranscriptJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Job deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"job_id":{"type":"string"}},"example":{"status":"deleted","job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}}}}},"/v1/transcript/jobs/{job_id}/upload-complete":{"post":{"tags":["Speech Intelligence"],"summary":"Confirm upload complete","description":"Confirm that the file has been uploaded to the presigned URL. This moves the job from `created` to `queued` and starts processing.\n\nThis endpoint is idempotent — if the job is already `queued`, `processing`, or `ready`, it returns the current status without re-queuing.\n\nThe server verifies that the file exists in S3 before accepting the confirmation. If the file is not found, returns 422.","operationId":"confirmTranscriptUpload","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Upload confirmed, processing started"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}},"422":{"description":"Upload not found or job expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Upload not found in storage, or job is in a terminal state"}}}}}}},"/v1/transcript/jobs/{job_id}/export":{"post":{"tags":["Speech Intelligence"],"summary":"Export transcription","description":"Generate an export artifact in the specified format. Returns a presigned download URL.\n\n**Formats:**\n- `txt` — Plain text transcript (auto-generated on completion)\n- `json` — Structured JSON with segments and timestamps (auto-generated on completion)\n- `srt` — SubRip subtitle format (generated on demand)\n- `vtt` — WebVTT subtitle format (generated on demand)\n\n`txt` and `json` are created automatically when a job reaches `ready` status. Calling this endpoint for those formats returns the existing artifact. `srt` and `vtt` are generated on first request and cached for subsequent calls.\n\nThe job must be in `ready` status. Returns 422 if the job has not completed processing.","operationId":"exportTranscript","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptExportRequest"}}}},"responses":{"200":{"description":"Export artifact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptArtifact"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}},"422":{"description":"Job is not in ready status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job is not in ready status. Current status: processing"}}}}}}},"/v1/sms/send":{"post":{"tags":["SMS"],"summary":"Send SMS","description":"Send SMS message(s) to one or more recipients.\n\n**Recipients:** Specify at least one of:\n- `to`: List of phone numbers (E.164 format, e.g., +4799999999)\n- `to_contacts`: List of contact UUIDs from your contact directory\n- `to_groups`: List of contact group UUIDs\n\n**Message Length:**\n- GSM-7 (standard): 160 chars = 1 SMS, then 153 chars per part\n- Unicode (emoji, etc.): 70 chars = 1 SMS, then 67 chars per part\n- Max 1600 chars (10 parts)\n\n**Scheduling:**\n- Use `scheduled_at` for future delivery (ISO 8601 format, max 30 days ahead)\n- By default, `scheduled_at` is interpreted as UTC\n- Use `timezone` parameter to specify local time (IANA format, e.g., \"Europe/Oslo\")\n- Scheduled messages can be cancelled using POST `/messages/{id}/cancel`\n\n**Delivery Reports:** Provide `callback_url` (HTTPS) to receive status webhooks.","operationId":"sendSms","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSMSRequest"}}}},"responses":{"200":{"description":"SMS queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSMSResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid phone number format. Use E.164 (e.g., +4712345678)"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient balance. Top up at my.wayscloud.services/billing"}}}},"403":{"description":"SMS service not activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"SMS service not activated. Activate at my.wayscloud.services"}}}}}}},"/v1/sms/messages":{"get":{"tags":["SMS"],"summary":"List messages","description":"List sent and received SMS messages with optional filters.","operationId":"listSmsMessages","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"direction","in":"query","schema":{"type":"string","enum":["OUTBOUND","INBOUND"]},"description":"Filter by direction"},{"name":"status","in":"query","schema":{"type":"string","enum":["QUEUED","SENT","DELIVERED","FAILED"]},"description":"Filter by status"},{"name":"from_date","in":"query","schema":{"type":"string","format":"date"},"description":"From date (YYYY-MM-DD)"},{"name":"to_date","in":"query","schema":{"type":"string","format":"date"},"description":"To date (YYYY-MM-DD)"},{"name":"page","in":"query","schema":{"type":"integer","default":1},"description":"Page number"},{"name":"page_size","in":"query","schema":{"type":"integer","default":50,"maximum":100},"description":"Items per page"}],"responses":{"200":{"description":"Message list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessageList"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/messages/{message_id}":{"get":{"tags":["SMS"],"summary":"Get message","description":"Get details for a specific SMS message.","operationId":"getSmsMessage","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Message ID"}],"responses":{"200":{"description":"Message details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessage"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Message not found"}}}}}}},"/v1/sms/messages/{message_id}/cancel":{"post":{"tags":["SMS"],"summary":"Cancel scheduled message","description":"Cancel a scheduled or queued SMS message before it is sent.\n\n**Cancellable statuses:**\n- `SCHEDULED` - Message waiting for scheduled time\n- `QUEUED` - Message in send queue\n\nMessages that are already `SENDING`, `SENT`, `DELIVERED`, or `FAILED` cannot be cancelled.","operationId":"cancelSmsMessage","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Message ID to cancel"}],"responses":{"200":{"description":"Message cancelled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"message_id":{"type":"string"}},"example":{"message":"Message cancelled","message_id":"550e8400-e29b-41d4-a716-446655440000"}}}}},"400":{"description":"Message cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Message cannot be cancelled. Status: DELIVERED"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Message not found"}}}}}}},"/v1/sms/sender-profiles":{"get":{"tags":["SMS"],"summary":"List sender profiles","description":"List your configured sender profiles (sender IDs).","operationId":"listSenderProfiles","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Sender profiles","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SenderProfile"},"example":[{"id":"550e8400-e29b-41d4-a716-446655440000","name":"marketing","sender_id":"MyBrand","is_default":false,"allow_reply":false,"is_custom_sender":true,"approval_status":"approved","monthly_price":99.0,"created_at":"2025-01-15T10:00:00Z"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["SMS"],"summary":"Create sender profile","description":"Create a new sender profile (sender ID).\n\n**Sender ID Types:**\n- **Phone number**: Your own number or a dedicated number\n- **Alphanumeric**: Brand name (max 11 chars, no ÆØÅ, requires brand_confirmation)\n\nCustom alphanumeric sender IDs require admin approval and may have a monthly fee.","operationId":"createSenderProfile","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSenderProfileRequest"}}}},"responses":{"201":{"description":"Profile created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderProfile"}}}},"400":{"description":"Invalid sender ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid sender ID. Alphanumeric max 11 chars, must start with letter."}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Profile name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"A sender profile with this name already exists"}}}}}}},"/v1/sms/sender-profiles/{profile_id}":{"delete":{"tags":["SMS"],"summary":"Delete sender profile","description":"Delete a sender profile. Cannot delete the default profile if it is the only one.","operationId":"deleteSenderProfile","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Profile ID"}],"responses":{"204":{"description":"Profile deleted"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Profile not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Sender profile not found"}}}},"409":{"description":"Cannot delete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Cannot delete: this is your only sender profile"}}}}}}},"/v1/sms/status":{"get":{"tags":["SMS"],"summary":"Get service status","description":"Get SMS service status including message counts and default sender.","operationId":"getSmsStatus","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Service status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSServiceStatus"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/usage":{"get":{"tags":["SMS"],"summary":"Get SMS usage for billing","description":"Get SMS usage statistics for a billing period.\n\nReturns message counts, SMS units consumed, and cost breakdown.\n\n**Pricing per SMS unit:**\n- NOK: 0.99\n- SEK: 0.99\n- DKK: 0.79\n- EUR: 0.10\n- USD: 0.10\n\nCost is calculated as: `units_used × price_per_unit`","operationId":"getSmsUsage","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"description":"Billing period in YYYY-MM format. Defaults to current month.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$","example":"2025-12"}}],"responses":{"200":{"description":"Usage statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSUsageResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/stats":{"get":{"tags":["SMS"],"summary":"Get SMS statistics","description":"Get aggregated SMS statistics across multiple time periods.\n\nReturns message counts, units, and delivery rates for:\n- Today\n- This week\n- This month\n- Last 30 days\n\nAlso includes a breakdown by message status (QUEUED, SENT, DELIVERED, FAILED, etc).","operationId":"getSmsStats","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/keywords":{"get":{"tags":["SMS Keywords"],"summary":"List keywords","description":"List your reserved keywords for inbound SMS routing.","operationId":"listSmsKeywords","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Keywords list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SMSKeyword"},"example":[{"id":"550e8400-e29b-41d4-a716-446655440000","keyword":"SUPPORT","description":"Customer support keyword","is_active":true,"webhook_url":"https://example.com/webhooks/sms","auto_reply_enabled":true,"auto_reply_message":"Thank you for contacting support. We will respond shortly.","messages_received":42,"created_at":"2025-01-15T10:00:00Z"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["SMS Keywords"],"summary":"Create keyword","description":"Reserve a keyword for inbound SMS routing.\n\nWhen someone sends an SMS starting with your keyword to a shared number, the message is routed to your webhook.\n\n**Example:** Reserve \"SUPPORT\" → User texts \"SUPPORT help me\" → Your webhook receives the message.","operationId":"createSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeywordRequest"}}}},"responses":{"201":{"description":"Keyword created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeyword"}}}},"400":{"description":"Invalid keyword format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid keyword format. Must be alphanumeric, 2-20 characters."}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Keyword already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword already reserved by another customer"}}}}}}},"/v1/sms/keywords/{keyword_id}":{"get":{"tags":["SMS Keywords"],"summary":"Get keyword","description":"Get details for a specific keyword.","operationId":"getSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Keyword ID"}],"responses":{"200":{"description":"Keyword details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeyword"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Keyword not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword not found"}}}}}},"patch":{"tags":["SMS Keywords"],"summary":"Update keyword","description":"Update keyword settings (webhook URL, auto-reply, etc.).","operationId":"updateSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Keyword ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateKeywordRequest"}}}},"responses":{"200":{"description":"Keyword updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeyword"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Keyword not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword not found"}}}}}},"delete":{"tags":["SMS Keywords"],"summary":"Delete keyword","description":"Release a keyword reservation.","operationId":"deleteSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Keyword ID"}],"responses":{"204":{"description":"Keyword deleted"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Keyword not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword not found"}}}}}}},"/v1/sms/keywords/check/{keyword}":{"get":{"tags":["SMS Keywords"],"summary":"Check keyword availability","description":"Check if a keyword is available for reservation.","operationId":"checkSmsKeywordAvailability","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword","in":"path","required":true,"schema":{"type":"string"},"description":"Keyword to check"}],"responses":{"200":{"description":"Availability status","content":{"application/json":{"schema":{"type":"object","properties":{"keyword":{"type":"string"},"available":{"type":"boolean"}},"example":{"keyword":"SUPPORT","available":true}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/iot/devices":{"get":{"tags":["IoT Platform"],"summary":"List devices","description":"Retrieve a paginated list of registered IoT devices.","operationId":"list_devices_v1_iot_devices_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number","default":1,"title":"Page"},"description":"Page number"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page","default":20,"title":"Page Size"},"description":"Items per page"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search in device name or ID","title":"Search"},"description":"Search in device name or ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceList"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"403":{"description":"API key lacks 'iot' permission"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Register device","description":"Register a new IoT device and generate MQTT credentials. Subject to plan device limits.","operationId":"register_device_v1_iot_devices_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCreateRequest"}}}},"responses":{"201":{"description":"Device registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDevice"}}}},"403":{"description":"Device limit reached"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}":{"get":{"tags":["IoT Platform"],"summary":"Get device","description":"Retrieve detailed information about a specific device.","operationId":"get_device_v1_iot_devices__device_id__get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDevice"}}}},"404":{"description":"Device not found"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"patch":{"tags":["IoT Platform"],"summary":"Update device","description":"Update device name, description, type, metadata, or active status.","operationId":"update_device_v1_iot_devices__device_id__patch","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDevice"}}}},"404":{"description":"Device not found"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete device","description":"Delete device and revoke MQTT credentials. Cannot be undone.","operationId":"delete_device_v1_iot_devices__device_id__delete","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"responses":{"204":{"description":"Device deleted"},"404":{"description":"Device not found"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/health":{"get":{"tags":["IoT Platform"],"summary":"Get device health","description":"Health score (0-100) with component breakdown: uptime, alarms, stability, freshness.","operationId":"get_device_health_v1_iot_devices__device_id__health_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceHealth"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/tags":{"get":{"tags":["IoT Platform"],"summary":"List device tags","operationId":"list_device_tags_v1_iot_devices__device_id__tags_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceTags"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Add tags to device","operationId":"add_device_tags_v1_iot_devices__device_id__tags_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"Tags to add","title":"Tags"},"example":["production","floor-3"]}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceTagsAdded"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/tags/{tag}":{"delete":{"tags":["IoT Platform"],"summary":"Remove tag from device","operationId":"remove_device_tag_v1_iot_devices__device_id__tags__tag__delete","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","description":"Tag to remove","title":"Tag"},"description":"Tag to remove"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups":{"get":{"tags":["IoT Platform"],"summary":"List device groups","operationId":"list_groups_v1_iot_groups_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroupList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create device group","operationId":"create_group_v1_iot_groups_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroup"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups/{group_id}":{"get":{"tags":["IoT Platform"],"summary":"Get device group","operationId":"get_group_v1_iot_groups__group_id__get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroup"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update device group","operationId":"update_group_v1_iot_groups__group_id__put","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroup"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete device group","operationId":"delete_group_v1_iot_groups__group_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups/{group_id}/devices":{"post":{"tags":["IoT Platform"],"summary":"Add devices to group","operationId":"add_devices_to_group_v1_iot_groups__group_id__devices_post","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"Device IDs to add","title":"Device Ids"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroupDevicesAdded"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups/{group_id}/devices/{device_id}":{"delete":{"tags":["IoT Platform"],"summary":"Remove device from group","operationId":"remove_device_from_group_v1_iot_groups__group_id__devices__device_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/profiles":{"get":{"tags":["IoT Platform"],"summary":"List device profiles","operationId":"list_profiles_v1_iot_profiles_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfileList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create device profile","operationId":"create_profile_v1_iot_profiles_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfile"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/profiles/{profile_id}":{"get":{"tags":["IoT Platform"],"summary":"Get device profile","operationId":"get_profile_v1_iot_profiles__profile_id__get","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfile"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update device profile","operationId":"update_profile_v1_iot_profiles__profile_id__put","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfile"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete device profile","operationId":"delete_profile_v1_iot_profiles__profile_id__delete","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/profiles/{profile_id}/apply/{device_id}":{"post":{"tags":["IoT Platform"],"summary":"Apply profile to device","operationId":"apply_profile_to_device_v1_iot_profiles__profile_id__apply__device_id__post","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}},{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfileApplyResult"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules":{"get":{"tags":["IoT Platform"],"summary":"List alarm rules","operationId":"list_rules_v1_iot_rules_get","parameters":[{"name":"scope_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by scope type","title":"Scope Type"},"description":"Filter by scope type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRuleList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create alarm rule","operationId":"create_rule_v1_iot_rules_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules/{rule_id}":{"get":{"tags":["IoT Platform"],"summary":"Get alarm rule","operationId":"get_rule_v1_iot_rules__rule_id__get","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update alarm rule","operationId":"update_rule_v1_iot_rules__rule_id__put","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete alarm rule","operationId":"delete_rule_v1_iot_rules__rule_id__delete","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules/{rule_id}/enable":{"post":{"tags":["IoT Platform"],"summary":"Enable alarm rule","operationId":"enable_rule_v1_iot_rules__rule_id__enable_post","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules/{rule_id}/disable":{"post":{"tags":["IoT Platform"],"summary":"Disable alarm rule","operationId":"disable_rule_v1_iot_rules__rule_id__disable_post","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms":{"get":{"tags":["IoT Platform"],"summary":"List alarms","description":"Retrieve alarms with optional filtering by status, severity, device, or rule.","operationId":"list_alarms_v1_iot_alarms_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: active, acknowledged, resolved, suppressed, expired","title":"Status"},"description":"Filter: active, acknowledged, resolved, suppressed, expired"},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: critical, warning, info","title":"Severity"},"description":"Filter: critical, warning, info"},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device ID","title":"Device Id"},"description":"Filter by device ID"},{"name":"rule_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rule ID","title":"Rule Id"},"description":"Filter by rule ID"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarmList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/summary":{"get":{"tags":["IoT Platform"],"summary":"Get alarm summary","description":"Alarm counts grouped by status and severity.","operationId":"alarm_summary_v1_iot_alarms_summary_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarmSummary"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}":{"get":{"tags":["IoT Platform"],"summary":"Get alarm details","operationId":"get_alarm_v1_iot_alarms__alarm_id__get","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/timeline":{"get":{"tags":["IoT Platform"],"summary":"Get alarm event timeline","operationId":"get_alarm_timeline_v1_iot_alarms__alarm_id__timeline_get","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarmTimeline"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/acknowledge":{"post":{"tags":["IoT Platform"],"summary":"Acknowledge alarm","operationId":"acknowledge_alarm_v1_iot_alarms__alarm_id__acknowledge_post","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmActionRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/resolve":{"post":{"tags":["IoT Platform"],"summary":"Resolve alarm","operationId":"resolve_alarm_v1_iot_alarms__alarm_id__resolve_post","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmActionRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/reopen":{"post":{"tags":["IoT Platform"],"summary":"Reopen a resolved alarm","operationId":"reopen_alarm_v1_iot_alarms__alarm_id__reopen_post","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmActionRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/datapoints":{"get":{"tags":["IoT Platform"],"summary":"List datapoint definitions","operationId":"list_datapoints_v1_iot_datapoints_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapointList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create datapoint definition","operationId":"create_datapoint_v1_iot_datapoints_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatapointCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapoint"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/datapoints/{datapoint_id}":{"get":{"tags":["IoT Platform"],"summary":"Get datapoint definition","operationId":"get_datapoint_v1_iot_datapoints__datapoint_id__get","parameters":[{"name":"datapoint_id","in":"path","required":true,"schema":{"type":"string","title":"Datapoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapoint"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update datapoint definition","operationId":"update_datapoint_v1_iot_datapoints__datapoint_id__put","parameters":[{"name":"datapoint_id","in":"path","required":true,"schema":{"type":"string","title":"Datapoint Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatapointUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapoint"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete datapoint definition","operationId":"delete_datapoint_v1_iot_datapoints__datapoint_id__delete","parameters":[{"name":"datapoint_id","in":"path","required":true,"schema":{"type":"string","title":"Datapoint Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/telemetry":{"get":{"tags":["IoT Platform"],"summary":"Get device telemetry","description":"Retrieve telemetry data for a device, optionally filtered by datapoint and time range.","operationId":"get_device_telemetry_v1_iot_devices__device_id__telemetry_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"datapoint_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by datapoint ID","title":"Datapoint Id"},"description":"Filter by datapoint ID"},{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"Hours of history to retrieve","default":24,"title":"Hours"},"description":"Hours of history to retrieve"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTTelemetryData"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/telemetry/latest":{"get":{"tags":["IoT Platform"],"summary":"Get latest telemetry values","operationId":"get_device_telemetry_latest_v1_iot_devices__device_id__telemetry_latest_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTTelemetryData"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/channels":{"get":{"tags":["IoT Platform"],"summary":"List notification channels","operationId":"list_notification_channels_v1_iot_notifications_channels_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannelList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create notification channel","operationId":"create_notification_channel_v1_iot_notifications_channels_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannel"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Name conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"A channel with this name already exists"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/channels/{channel_id}":{"get":{"tags":["IoT Platform"],"summary":"Get notification channel","operationId":"get_notification_channel_v1_iot_notifications_channels__channel_id__get","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannel"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update notification channel","operationId":"update_notification_channel_v1_iot_notifications_channels__channel_id__put","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannel"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete notification channel","operationId":"delete_notification_channel_v1_iot_notifications_channels__channel_id__delete","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/channels/{channel_id}/test":{"post":{"tags":["IoT Platform"],"summary":"Send test notification","operationId":"test_notification_channel_v1_iot_notifications_channels__channel_id__test_post","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannelTestResult"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/policies":{"get":{"tags":["IoT Platform"],"summary":"List notification policies","operationId":"list_notification_policies_v1_iot_notifications_policies_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicyList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create notification policy","operationId":"create_notification_policy_v1_iot_notifications_policies_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicy"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/policies/{policy_id}":{"get":{"tags":["IoT Platform"],"summary":"Get notification policy","operationId":"get_notification_policy_v1_iot_notifications_policies__policy_id__get","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicy"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update notification policy","operationId":"update_notification_policy_v1_iot_notifications_policies__policy_id__put","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicy"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete notification policy","operationId":"delete_notification_policy_v1_iot_notifications_policies__policy_id__delete","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/deliveries":{"get":{"tags":["IoT Platform"],"summary":"List notification deliveries","description":"Delivery history with optional filtering by channel type and status.","operationId":"list_notification_deliveries_v1_iot_notifications_deliveries_get","parameters":[{"name":"channel_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by channel type","title":"Channel Type"},"description":"Filter by channel type"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: sent, failed, pending, skipped","title":"Status"},"description":"Filter: sent, failed, pending, skipped"},{"name":"created_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 datetime – only return deliveries created after this time","title":"Created After"},"description":"ISO 8601 datetime – only return deliveries created after this time"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeliveryList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/credentials":{"get":{"tags":["IoT Platform"],"summary":"Get MQTT connection info","description":"Returns MQTT broker connection details. Per-device credentials are provided at registration.","operationId":"get_mqtt_credentials_v1_iot_credentials_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTCredentials"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/subscription":{"get":{"tags":["IoT Platform"],"summary":"Get IoT subscription","description":"Returns current plan, device limit, and usage.","operationId":"get_subscription_v1_iot_subscription_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTSubscription"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/usage":{"get":{"tags":["IoT Platform"],"summary":"Get usage statistics","description":"Returns message counts, data usage, and device activity for the current billing period.","operationId":"get_usage_v1_iot_usage_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTUsage"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/stats":{"get":{"tags":["IoT Platform"],"summary":"Get platform statistics","description":"Overview stats: total devices, active, connected, messages, data usage.","operationId":"get_stats_v1_iot_stats_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTStats"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/chatbot/api/bots":{"get":{"tags":["Chatbot"],"summary":"List chatbots","description":"Returns all active chatbots owned by the authenticated customer. Use the `id` or `slug` from this response as the `bot_id` parameter when calling the chat endpoints.","security":[{"ServiceAuth":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string"},"description":"WAYSCloud API key with `chatbot` permission."}],"responses":{"200":{"description":"List of active chatbots.","content":{"application/json":{"schema":{"type":"object","properties":{"bots":{"type":"array","description":"Active chatbots for this customer.","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique chatbot identifier."},"slug":{"type":"string","description":"URL-friendly identifier. Can be used as `bot_id` in chat requests."},"name":{"type":"string","description":"Display name of the chatbot."},"status":{"type":"string","description":"Current status of the chatbot."},"language":{"type":"string","description":"Primary language (ISO 639-1)."}}}}}},"example":{"bots":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","slug":"support-bot","name":"Customer Support","status":"active","language":"en"}]}}}},"401":{"description":"Missing, invalid, or unauthorized API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/chatbot/api/chat":{"post":{"tags":["Chatbot"],"summary":"Send a message","description":"Send a message to a chatbot and receive a generated response based on the configured knowledge sources. The response includes the answer, relevant source citations, and usage metadata.","security":[{"ServiceAuth":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string"},"description":"WAYSCloud API key with `chatbot` permission."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bot_id","message"],"properties":{"bot_id":{"type":"string","description":"Chatbot identifier. Accepts either the UUID or the slug (e.g. `support-bot`)."},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"The message to send to the chatbot."},"session_id":{"type":"string","nullable":true,"description":"Optional session identifier to continue an existing conversation. If omitted, a new session is created automatically. Reuse the returned `session_id` in subsequent requests to maintain conversation context."}},"example":{"bot_id":"support-bot","message":"What are your prices?"}}}}},"responses":{"200":{"description":"Generated response from the chatbot.","content":{"application/json":{"schema":{"type":"object","required":["session_id","message_id","response"],"properties":{"session_id":{"type":"string","format":"uuid","description":"Session identifier. Reuse this in subsequent requests to continue the same conversation."},"message_id":{"type":"string","format":"uuid","description":"Unique identifier for this response."},"response":{"type":"string","description":"Generated reply from the chatbot based on its knowledge sources."},"citations":{"type":"array","description":"Sources referenced in the response, when available. May be empty if the answer was generated without specific source matches.","items":{"type":"object","properties":{"title":{"type":"string","description":"Title of the source document."},"url":{"type":"string","description":"URL of the source."},"snippet":{"type":"string","description":"Relevant excerpt from the source."}}}},"tokens_input":{"type":"integer","description":"Number of input tokens consumed."},"tokens_output":{"type":"integer","description":"Number of output tokens generated."},"response_time_ms":{"type":"integer","description":"Total processing time in milliseconds."}},"example":{"session_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","message_id":"f1e2d3c4-b5a6-7890-abcd-ef1234567890","response":"Our Standard plan starts at 199 NOK/month and includes 3 chatbots with up to 3,000 messages per month.","citations":[{"title":"Pricing","url":"https://example.com/pricing","snippet":"Standard plan from 199 NOK/month..."}],"tokens_input":245,"tokens_output":87,"response_time_ms":2800}}}}},"401":{"description":"Missing, invalid, or unauthorized API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Chatbot not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Chatbot not found or not accessible"}}}},"429":{"description":"Rate limit or quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Rate limit exceeded. Retry after 60 seconds."}}}}}}},"/v1/chatbot/api/chat/stream":{"post":{"tags":["Chatbot"],"summary":"Send a message (streaming)","description":"Send a message and receive a real-time streaming response via Server-Sent Events (SSE). Tokens are delivered incrementally as they are generated.\n\nThe first event is always `meta`, followed by zero or more `token` events, and finally `done`. If an error occurs during generation, an `error` event is sent instead of `done`.\n\n**Event types:**\n\n| Type | Description |\n|------|-------------|\n| `meta` | Sent once at the start. Contains session ID and source citations. |\n| `token` | Sent for each generated token. |\n| `done` | Sent when generation is complete. |\n| `error` | Sent if an error occurs. Contains a human-readable message. |\n\n**Example stream:**\n```\ndata: {\"type\":\"meta\",\"data\":{\"session_id\":\"a1b2...\",\"citations\":[{\"title\":\"Pricing\",\"url\":\"https://example.com/pricing\"}]}}\ndata: {\"type\":\"token\",\"content\":\"Our\"}\ndata: {\"type\":\"token\",\"content\":\" prices\"}\ndata: {\"type\":\"token\",\"content\":\" start\"}\ndata: {\"type\":\"token\",\"content\":\" from\"}\ndata: {\"type\":\"done\"}\n```\n\n**Error example:**\n```\ndata: {\"type\":\"error\",\"content\":\"Service temporarily unavailable\"}\n```","security":[{"ServiceAuth":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string"},"description":"WAYSCloud API key with `chatbot` permission."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bot_id","message"],"properties":{"bot_id":{"type":"string","description":"Chatbot identifier. Accepts either the UUID or the slug."},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"The message to send to the chatbot."},"session_id":{"type":"string","nullable":true,"description":"Optional session identifier to continue an existing conversation. Reuse the `session_id` from a previous response to maintain context."}},"example":{"bot_id":"support-bot","message":"Tell me about your services"}}}}},"responses":{"200":{"description":"Streaming response via Server-Sent Events. Events are JSON objects prefixed with `data: `.","content":{"text/event-stream":{"schema":{"type":"string","description":"JSON-encoded SSE events in the order: meta → token* → done (or error)."}}}},"401":{"description":"Missing, invalid, or unauthorized API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Chatbot not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Chatbot not found or not accessible"}}}},"429":{"description":"Rate limit or quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Rate limit exceeded. Retry after 60 seconds."}}}}}}},"/v1/live-events/watch/{slug}":{"get":{"tags":["Live Events"],"summary":"Resolve watch state","description":"Returns the current watch state for an event. The watch page polls this every 10 seconds to detect state changes (e.g. event goes live, replay becomes available).\n\nReturns `watch_state` (viewer-facing UI state) and `status` (internal lifecycle). Playback objects are only included for public events — auth-required events must call verify-password or verify-token first.\n\nDraft events are not publicly resolvable and return 404.","operationId":"resolveWatchState","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"product-demo"},"description":"Event URL slug from the watch page URL"}],"responses":{"200":{"description":"Event state resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchStateResponse"},"examples":{"live":{"summary":"Public event, currently live","value":{"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo","name":"Product Demo","description":"Live demo of new release features","status":"live","watch_state":"live","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null,"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":null}},"not_started":{"summary":"Scheduled event, not yet live","value":{"event_id":"c1f4e8a2-5b3d-4f6a-9c8e-2d7f1a3b5c9e","slug":"spring-webinar","name":"Spring Release Webinar","description":"Quarterly product update for customers","status":"scheduled","watch_state":"not_started","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-15T14:00:00+00:00","scheduled_end_at":"2026-04-15T15:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":true,"playback_mode":"none","live_playback":null,"replay_playback":null,"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":null}},"replay":{"summary":"Ended event with replay","value":{"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo","name":"Product Demo","description":"Live demo of new release features","status":"ended","watch_state":"replay_available","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":"2026-04-10T13:28:45+00:00","timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T13:28:45+00:00"},"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":"2026-04-10T13:35:00+00:00"}},"access_required":{"summary":"Token-protected event","value":{"event_id":"b2d4f6a8-1c3e-5a7b-9d0f-4e6a8c2b1d3f","slug":"board-meeting-q2","name":"Q2 Board Meeting","description":null,"status":"live","watch_state":"access_required","visibility_mode":"token","requires_auth":true,"auth_type":"token","scheduled_start_at":"2026-04-10T09:00:00+00:00","scheduled_end_at":"2026-04-10T11:00:00+00:00","actual_start_at":"2026-04-10T09:02:30+00:00","actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"none","live_playback":null,"replay_playback":null,"embed_enabled":false,"banner_url":null,"logo_url":null,"replay_published_at":null}}}}}},"404":{"description":"Event not found. The slug does not exist, or the event is in draft status and not publicly resolvable.","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/{slug}/verify-password":{"post":{"tags":["Live Events"],"summary":"Verify event password","description":"Verifies the password for a password-protected event. On success, issues a short-lived access grant and returns the current playback information.\n\nFailed attempts are logged for abuse detection.","operationId":"verifyPassword","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"private-webinar"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","minLength":1,"description":"Event password set by the event organizer"}}},"example":{"password":"MyEventPass2026"}}}},"responses":{"200":{"description":"Password correct. Access grant issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"},"examples":{"live_event":{"summary":"Event is currently live","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T15:00:00+00:00","watch_state":"live","playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null}},"replay_available":{"summary":"Event ended, replay available","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_dGTNLjOFmQh_iaJUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T18:00:00+00:00","watch_state":"replay_available","playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T18:00:00+00:00"}}}}}}},"400":{"description":"Event is not password-protected","content":{"application/json":{"example":{"detail":"Event is not password-protected"}}}},"403":{"description":"Invalid password","content":{"application/json":{"example":{"detail":"Invalid password"}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/{slug}/verify-token":{"post":{"tags":["Live Events"],"summary":"Verify watch token","description":"Validates an access token for a token-gated event. On success, issues a short-lived access grant and returns playback information.\n\nThe token `use_count` is **not** incremented here — it is consumed when playback actually starts via the consume-grant endpoint. This means opening the watch page and entering a token does not use up a token slot.\n\nThe token can also be supplied as a URL parameter on the watch page (`?token=...`) for auto-verification.","operationId":"verifyToken","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"board-meeting-q2"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":1,"description":"Full watch token value as received when the token was created"}}},"example":{"token":"wayscloud_le_k5q2ubCC8QAx1L2RNNMn8dLQ7bEeVNTm"}}}},"responses":{"200":{"description":"Token valid. Access grant issued. Token **not consumed** yet — call consume-grant when playback starts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"},"examples":{"live_event":{"summary":"Event is currently live","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T15:00:00+00:00","watch_state":"live","playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null}},"replay_available":{"summary":"Event ended, replay available","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_dGTNLjOFmQh_iaJUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T18:00:00+00:00","watch_state":"replay_available","playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T18:00:00+00:00"}}}}}}},"400":{"description":"Event is not token-protected","content":{"application/json":{"example":{"detail":"Event is not token-protected"}}}},"403":{"description":"Invalid token, expired, or usage limit reached","content":{"application/json":{"examples":{"invalid":{"summary":"Token not found","value":{"detail":"Invalid token"}},"expired":{"summary":"Token past expiry","value":{"detail":"Token expired"}},"limit":{"summary":"All uses consumed","value":{"detail":"Token usage limit reached"}}}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/{slug}/consume-grant":{"post":{"tags":["Live Events"],"summary":"Consume access grant on playback start","description":"Called by the watch page when video playback actually starts. Marks the access grant as consumed and increments the token `use_count`.\n\nThis is the point where a token use is actually spent. Idempotent — calling twice with the same grant returns `already_consumed: true` without incrementing again.\n\nIf the grant has expired, returns 403.","operationId":"consumeAccessGrant","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"board-meeting-q2"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["access_grant"],"properties":{"access_grant":{"type":"string","description":"Access grant string (prefixed with `ag_`) received from verify-password or verify-token"}}},"example":{"access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL"}}}},"responses":{"200":{"description":"Grant consumed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsumeGrantResponse"},"examples":{"consumed":{"summary":"First consumption","value":{"consumed":true,"already_consumed":false}},"idempotent":{"summary":"Already consumed (idempotent)","value":{"consumed":true,"already_consumed":true}}}}}},"403":{"description":"Invalid or expired access grant","content":{"application/json":{"examples":{"invalid":{"summary":"Grant not found","value":{"detail":"Invalid access grant"}},"expired":{"summary":"Grant expired","value":{"detail":"Access grant expired"}}}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/by-id/{public_id}":{"get":{"tags":["Live Events"],"summary":"Resolve watch state","description":"Returns the current watch state for an event. The watch page polls this every 10 seconds to detect state changes (e.g. event goes live, replay becomes available).\n\nReturns `watch_state` (viewer-facing UI state) and `status` (internal lifecycle). Playback objects are only included for public events — auth-required events must call verify-password or verify-token first.\n\nDraft events are not publicly resolvable and return 404.\n\nThis tenant-independent variant is for Meil Live and verified customer audience domains. Do not use a customer-local slug for new integrations.","operationId":"resolveWatchStateByPublicId","parameters":[{"name":"public_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436"},"description":"Global public event identity. Use this in Meil Live and branded audience URLs."}],"responses":{"200":{"description":"Event state resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchStateResponse"},"examples":{"live":{"summary":"Public event, currently live","value":{"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo","name":"Product Demo","description":"Live demo of new release features","status":"live","watch_state":"live","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null,"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":null}},"not_started":{"summary":"Scheduled event, not yet live","value":{"event_id":"c1f4e8a2-5b3d-4f6a-9c8e-2d7f1a3b5c9e","slug":"spring-webinar","name":"Spring Release Webinar","description":"Quarterly product update for customers","status":"scheduled","watch_state":"not_started","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-15T14:00:00+00:00","scheduled_end_at":"2026-04-15T15:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":true,"playback_mode":"none","live_playback":null,"replay_playback":null,"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":null}},"replay":{"summary":"Ended event with replay","value":{"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo","name":"Product Demo","description":"Live demo of new release features","status":"ended","watch_state":"replay_available","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":"2026-04-10T13:28:45+00:00","timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T13:28:45+00:00"},"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":"2026-04-10T13:35:00+00:00"}},"access_required":{"summary":"Token-protected event","value":{"event_id":"b2d4f6a8-1c3e-5a7b-9d0f-4e6a8c2b1d3f","slug":"board-meeting-q2","name":"Q2 Board Meeting","description":null,"status":"live","watch_state":"access_required","visibility_mode":"token","requires_auth":true,"auth_type":"token","scheduled_start_at":"2026-04-10T09:00:00+00:00","scheduled_end_at":"2026-04-10T11:00:00+00:00","actual_start_at":"2026-04-10T09:02:30+00:00","actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"none","live_playback":null,"replay_playback":null,"embed_enabled":false,"banner_url":null,"logo_url":null,"replay_published_at":null}}}}}},"404":{"description":"Event not found. The slug does not exist, or the event is in draft status and not publicly resolvable.","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/by-id/{public_id}/verify-password":{"post":{"tags":["Live Events"],"summary":"Verify event password","description":"Verifies the password for a password-protected event. On success, issues a short-lived access grant and returns the current playback information.\n\nFailed attempts are logged for abuse detection.\n\nThis tenant-independent variant is for Meil Live and verified customer audience domains. Do not use a customer-local slug for new integrations.","operationId":"verifyPasswordByPublicId","parameters":[{"name":"public_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436"},"description":"Global public event identity. Use this in Meil Live and branded audience URLs."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","minLength":1,"description":"Event password set by the event organizer"}}},"example":{"password":"MyEventPass2026"}}}},"responses":{"200":{"description":"Password correct. Access grant issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"},"examples":{"live_event":{"summary":"Event is currently live","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T15:00:00+00:00","watch_state":"live","playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null}},"replay_available":{"summary":"Event ended, replay available","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_dGTNLjOFmQh_iaJUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T18:00:00+00:00","watch_state":"replay_available","playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T18:00:00+00:00"}}}}}}},"400":{"description":"Event is not password-protected","content":{"application/json":{"example":{"detail":"Event is not password-protected"}}}},"403":{"description":"Invalid password","content":{"application/json":{"example":{"detail":"Invalid password"}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/by-id/{public_id}/verify-token":{"post":{"tags":["Live Events"],"summary":"Verify watch token","description":"Validates an access token for a token-gated event. On success, issues a short-lived access grant and returns playback information.\n\nThe token `use_count` is **not** incremented here — it is consumed when playback actually starts via the consume-grant endpoint. This means opening the watch page and entering a token does not use up a token slot.\n\nThe token can also be supplied as a URL parameter on the watch page (`?token=...`) for auto-verification.\n\nThis tenant-independent variant is for Meil Live and verified customer audience domains. Do not use a customer-local slug for new integrations.","operationId":"verifyTokenByPublicId","parameters":[{"name":"public_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436"},"description":"Global public event identity. Use this in Meil Live and branded audience URLs."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":1,"description":"Full watch token value as received when the token was created"}}},"example":{"token":"wayscloud_le_k5q2ubCC8QAx1L2RNNMn8dLQ7bEeVNTm"}}}},"responses":{"200":{"description":"Token valid. Access grant issued. Token **not consumed** yet — call consume-grant when playback starts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"},"examples":{"live_event":{"summary":"Event is currently live","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T15:00:00+00:00","watch_state":"live","playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null}},"replay_available":{"summary":"Event ended, replay available","value":{"verified":true,"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","access_grant":"ag_dGTNLjOFmQh_iaJUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T18:00:00+00:00","watch_state":"replay_available","playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T18:00:00+00:00"}}}}}}},"400":{"description":"Event is not token-protected","content":{"application/json":{"example":{"detail":"Event is not token-protected"}}}},"403":{"description":"Invalid token, expired, or usage limit reached","content":{"application/json":{"examples":{"invalid":{"summary":"Token not found","value":{"detail":"Invalid token"}},"expired":{"summary":"Token past expiry","value":{"detail":"Token expired"}},"limit":{"summary":"All uses consumed","value":{"detail":"Token usage limit reached"}}}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/by-id/{public_id}/consume-grant":{"post":{"tags":["Live Events"],"summary":"Consume access grant on playback start","description":"Called by the watch page when video playback actually starts. Marks the access grant as consumed and increments the token `use_count`.\n\nThis is the point where a token use is actually spent. Idempotent — calling twice with the same grant returns `already_consumed: true` without incrementing again.\n\nIf the grant has expired, returns 403.\n\nThis tenant-independent variant is for Meil Live and verified customer audience domains. Do not use a customer-local slug for new integrations.","operationId":"consumeAccessGrantByPublicId","parameters":[{"name":"public_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436"},"description":"Global public event identity. Use this in Meil Live and branded audience URLs."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["access_grant"],"properties":{"access_grant":{"type":"string","description":"Access grant string (prefixed with `ag_`) received from verify-password or verify-token"}}},"example":{"access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL"}}}},"responses":{"200":{"description":"Grant consumed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsumeGrantResponse"},"examples":{"consumed":{"summary":"First consumption","value":{"consumed":true,"already_consumed":false}},"idempotent":{"summary":"Already consumed (idempotent)","value":{"consumed":true,"already_consumed":true}}}}}},"403":{"description":"Invalid or expired access grant","content":{"application/json":{"examples":{"invalid":{"summary":"Grant not found","value":{"detail":"Invalid access grant"}},"expired":{"summary":"Grant expired","value":{"detail":"Access grant expired"}}}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events":{"get":{"tags":["Live Events"],"summary":"List events","description":"Returns a paginated list of the authenticated customer's events, ordered by creation date descending.","operationId":"listEvents","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["draft","scheduled","ready","rehearsal","live","ending","ended","processing","failed","cancelled"]},"description":"Filter by lifecycle status"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100},"description":"Max results"},{"name":"offset","in":"query","schema":{"type":"integer","default":0},"description":"Pagination offset"}],"responses":{"200":{"description":"Event list","content":{"application/json":{"example":{"events":[{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"ready","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}],"total":12,"page":1,"page_size":20}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["Live Events"],"summary":"Create event","description":"Creates a new live event, generates RTMP stream credentials, and activates the `live_events` service if this is the customer's first event.\n\n**Important:** The `stream_key` is returned only at creation. Store it securely.","operationId":"createEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventCreateRequest"},"example":{"name":"Product Demo","description":"Live demo of new release features","scheduled_start_at":"2026-04-10T12:00:00Z","scheduled_end_at":"2026-04-10T13:30:00Z","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"embed_enabled":true}}}},"responses":{"201":{"description":"Event created. Stream key shown once.","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"draft","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00","stream_key":"qz0bKkTt6Vjvpb7_yQxMXXvpBzohJm0MlCL4V-PQrAU"}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Slug conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"An event with this slug already exists"}}}},"429":{"description":"Plan event limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Plan event limit exceeded. Delete old events or upgrade your plan."}}}}}}},"/v1/live-events/events/{event_id}":{"get":{"tags":["Live Events"],"summary":"Get event","description":"Returns full event detail including ingest status, current session, and recording count.","operationId":"getEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event detail","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"ready","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}},"patch":{"tags":["Live Events"],"summary":"Update event","description":"Update event properties. **Only allowed when status is `draft`, `scheduled`, or `ready`.** Events that are live, ending, or processing cannot be modified.","operationId":"updateEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventUpdateRequest"},"example":{"name":"Updated Demo Title","description":"New description","visibility_mode":"password","password":"MyEventPass2026"}}}},"responses":{"200":{"description":"Event updated","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Updated Demo Title","description":"Live demo of new release features","status":"ready","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"password","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}},"409":{"description":"Event state conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event cannot be modified while live or processing"}}}}}},"delete":{"tags":["Live Events"],"summary":"Delete event","description":"Soft-deletes the event. Retained for 30 days before permanent deletion. **Not allowed while `live`, `ending`, or `processing`.**","operationId":"deleteEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"204":{"description":"Event deleted"},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}},"409":{"description":"Event state conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event is currently live or processing"}}}}}}},"/v1/live-events/events/{event_id}/rehearsal/start":{"post":{"tags":["Live Events"],"summary":"Start rehearsal","description":"Transitions event to `rehearsal`. The encoder can connect and stream, but the watch page shows \"in rehearsal\" to viewers.\n\n**Precondition:** Status must be `ready`.","operationId":"startRehearsal","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Rehearsal started","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"rehearsal","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"409":{"description":"Event not ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event must be in ready state to start rehearsal"}}}}}}},"/v1/live-events/events/{event_id}/live/start":{"post":{"tags":["Live Events"],"summary":"Go live","description":"Transitions event to `live`. Stream becomes visible to viewers. Recording starts if `record_enabled`.\n\n**Precondition:** Status must be `ready` or `rehearsal`.","operationId":"startLive","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event is now live","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"live","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"active","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"409":{"description":"Event not ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event must be in ready or rehearsal state to go live"}}}}}}},"/v1/live-events/events/{event_id}/live/stop":{"post":{"tags":["Live Events"],"summary":"Stop live","description":"Stops the live event. Disconnects encoder, ends sessions, transitions to `ending`. Recordings are finalized automatically.\n\n**Precondition:** Status must be `live` or `rehearsal`.","operationId":"stopLive","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event stopping","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","public_id":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"ending","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":"2026-04-10T13:28:45+00:00","timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","public_watch_url":"https://live.onmeil.eu/e/4ee07f75-9be5-4ed6-a43f-4ad9319c6436","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"disconnected","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"409":{"description":"Event not live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event must be live or in rehearsal to stop"}}}}}}},"/v1/live-events/events/{event_id}/stream-key/rotate":{"post":{"tags":["Live Events"],"summary":"Rotate stream key","description":"Generates a new stream key and invalidates the old one immediately. Encoder must be reconfigured.\n\n**Precondition:** Status must NOT be `live`, `ending`, or `processing`.\n\n**Important:** The new `stream_key` is shown only once in this response.","operationId":"rotateStreamKey","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Stream key rotated","content":{"application/json":{"example":{"stream_key":"Nw7pLm2kHxR9sBq_3TcFvYzA8dGjWe1nUoKi5MhXrQY","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"Nw7pLm2k"}}}},"409":{"description":"Cannot rotate during live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Cannot rotate stream key while event is live"}}}}}}},"/v1/live-events/events/{event_id}/recordings":{"get":{"tags":["Live Events"],"summary":"List recordings","description":"Returns recordings for the event. Each live session produces one recording when `record_enabled` is true.","operationId":"listRecordings","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Recordings list","content":{"application/json":{"example":{"recordings":[{"id":"rec-7f3c1c2a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","session_id":"f6e170db-0f68-4dfd-9239-1a7806d24fdb","format":"mp4","duration_seconds":5255,"size_bytes":1073741824,"thumbnail_url":"https://s3.wayscloud.services/media-events/tenant/.../thumbnail.jpg?X-Amz-Expires=604800","replay_url":"https://s3.wayscloud.services/media-events/tenant/.../recording.mp4?X-Amz-Expires=604800","processing_status":"completed","created_at":"2026-04-10T13:35:00+00:00"}]}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/tokens":{"post":{"tags":["Live Events"],"summary":"Create watch token","description":"Creates an access token for token-gated events (`visibility_mode: token`). The `token` value is returned only at creation.\n\n`max_uses` counts actual playback sessions (via access-grant model), not token form submissions.","operationId":"createWatchToken","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventTokenCreateRequest"},"example":{"label":"VIP Attendees","max_uses":50,"expires_at":"2026-04-10T18:00:00Z"}}}},"responses":{"201":{"description":"Token created. Full token shown once.","content":{"application/json":{"example":{"id":"tok-a1b2c3d4-e5f6-7890-abcd-ef1234567890","token":"wayscloud_le_k5q2ubCC8QAx1L2RNNMn8dLQ7bEeVNTm","token_prefix":"wayscloud_le","label":"VIP Attendees","max_uses":50,"used_count":0,"valid_from":"2026-04-08T10:00:00+00:00","expires_at":"2026-04-10T18:00:00+00:00","revoked_at":null,"created_at":"2026-04-08T10:00:00+00:00"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}},"get":{"tags":["Live Events"],"summary":"List watch tokens","description":"Returns all watch tokens for the event. Token values are masked — only the prefix is shown.","operationId":"listWatchTokens","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Token list","content":{"application/json":{"example":{"tokens":[{"id":"tok-a1b2c3d4-e5f6-7890-abcd-ef1234567890","token_prefix":"wayscloud_le","label":"VIP Attendees","max_uses":50,"used_count":12,"valid_from":"2026-04-08T10:00:00+00:00","expires_at":"2026-04-10T18:00:00+00:00","revoked_at":null,"created_at":"2026-04-08T10:00:00+00:00"}]}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/stats":{"get":{"tags":["Live Events"],"summary":"Get event statistics","description":"Returns aggregated statistics for the event across all sessions.","operationId":"getEventStats","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event statistics","content":{"application/json":{"example":{"total_sessions":3,"total_duration_seconds":8520,"peak_concurrent_viewers":312,"total_unique_viewers":845,"total_recordings":2,"total_recording_size_bytes":2147483648}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/limits":{"get":{"tags":["Live Events"],"summary":"Get plan limits","description":"Returns the customer's current plan capabilities and usage for Live Events.","operationId":"getPlanLimits","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"responses":{"200":{"description":"Plan capabilities","content":{"application/json":{"example":{"plan":"Live Events Pro","max_events_per_month":20,"events_used_this_month":3,"max_concurrent_events":2,"max_duration_minutes":480,"max_bitrate_kbps":8000,"max_resolution":"1080p","recording_enabled":true,"replay_enabled":true,"adaptive_bitrate_enabled":true,"custom_domains_enabled":false,"simulcast_enabled":false,"transcript_enabled":false,"retention_days_default":90,"visibility_modes_allowed":["public","token","password","tenant_only"]}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/live-events/events/{event_id}/ingest":{"get":{"tags":["Live Events"],"summary":"Get ingest details","description":"Returns ingest configuration for the event. Use this to configure your encoder (OBS, vMix, ffmpeg, etc.).","operationId":"getIngestDetails","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Ingest configuration","content":{"application/json":{"example":{"protocol":"rtmp","rtmp_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","encoder_connected":false,"last_seen_at":null,"stream_status":"inactive"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/health":{"get":{"tags":["Live Events"],"summary":"Get event health","description":"Returns operator-friendly health signals for a live event. Data is **best-effort** — derived from periodic media server polling and may be slightly delayed.\n\nDo not use for billing or SLA calculations.","operationId":"getEventHealth","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Health signals","content":{"application/json":{"examples":{"healthy":{"summary":"Healthy live event","value":{"encoder_connected":true,"audio_detected":true,"video_detected":true,"current_resolution":"1920x1080","current_bitrate_kbps":4500,"recording_active":true,"session_status":"active","last_media_heartbeat_at":"2026-04-10T12:15:30+00:00","degraded":false,"operator_message":null,"viewers":47}},"degraded":{"summary":"Degraded — low bitrate","value":{"encoder_connected":true,"audio_detected":true,"video_detected":true,"current_resolution":"1280x720","current_bitrate_kbps":380,"recording_active":true,"session_status":"active","last_media_heartbeat_at":"2026-04-10T12:15:30+00:00","degraded":true,"operator_message":"Source bitrate unusually low","viewers":12}}}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/sessions":{"get":{"tags":["Live Events"],"summary":"List sessions","description":"Returns the history of live sessions for the event. Each time an encoder connects and goes live, a new session is created.","operationId":"listSessions","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Session history","content":{"application/json":{"example":{"sessions":[{"id":"f6e170db-0f68-4dfd-9239-1a7806d24fdb","session_number":1,"status":"ended","started_at":"2026-04-10T12:01:10+00:00","ended_at":"2026-04-10T13:28:45+00:00","ended_reason":"operator_stop","source_resolution":"1920x1080","source_bitrate_kbps":4500,"source_codec_video":"h264","source_codec_audio":"aac","peak_viewers":312,"recording_id":"rec-7f3c1c2a-4b5d-6e7f-8a9b-0c1d2e3f4a5b"}]}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/ip/{ip}":{"get":{"tags":["IP Intelligence"],"summary":"IP summary","description":"Returns geolocation, network identity, threat assessment, and detection flags for an IP address. Free tier auto-provisioned on first use.","operationId":"getIpSummary","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"8.8.8.8"}],"responses":{"200":{"description":"Complete IP intelligence summary","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"Queried IP address"},"ip_version":{"type":"integer","enum":[4,6],"description":"IP protocol version"},"hostname":{"type":"string","nullable":true,"description":"Reverse DNS hostname (null if no rDNS record)"},"geo":{"type":"object","description":"Geolocation data","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code"},"country_name":{"type":"string","description":"Human-readable country name"},"city":{"type":"string","nullable":true,"description":"City name"},"region":{"type":"string","nullable":true,"description":"Region or state"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"timezone":{"type":"string","nullable":true,"description":"IANA timezone"}}},"network":{"type":"object","description":"Network identity","properties":{"asn":{"type":"integer","nullable":true,"description":"Autonomous System Number"},"isp":{"type":"string","nullable":true,"description":"Internet Service Provider"},"org":{"type":"string","nullable":true,"description":"Organization name"},"connection_type":{"type":"string","enum":["residential","mobile","business","datacenter","hosting","education","government","unknown"],"description":"Connection classification"}}},"threat":{"type":"object","description":"Threat assessment","properties":{"score":{"type":"number","minimum":0,"maximum":100,"description":"Threat score (0 = clean, 100 = critical)"},"level":{"type":"string","enum":["clean","low","medium","high","critical"],"description":"Human-readable threat level"},"is_clean":{"type":"boolean","description":"True if no known threat reports"}}},"flags":{"type":"object","description":"Detection flags","properties":{"vpn":{"type":"boolean","description":"Detected as VPN endpoint"},"proxy":{"type":"boolean","description":"Detected as proxy"},"tor":{"type":"boolean","description":"Detected as Tor exit node"},"datacenter":{"type":"boolean","description":"Hosted in datacenter or cloud"},"botnet":{"type":"boolean","description":"Associated with botnet activity"}}}},"example":{"ip":"8.8.8.8","ip_version":4,"hostname":"dns.google","geo":{"country":"US","country_name":"United States","city":"Mountain View","region":"California","latitude":37.386,"longitude":-122.0838,"timezone":"America/Los_Angeles"},"network":{"asn":15169,"isp":"Google LLC","org":"Google LLC","connection_type":"datacenter"},"threat":{"score":0,"level":"clean","is_clean":true},"flags":{"vpn":false,"proxy":false,"tor":false,"datacenter":true,"botnet":false}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/{ip}/geo":{"get":{"tags":["IP Intelligence"],"summary":"IP geolocation","description":"Returns geolocation, reverse DNS, and ASN information.","operationId":"getIpGeo","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"8.8.8.8"}],"responses":{"200":{"description":"Geolocation, reverse DNS, and network identity","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"Queried IP address"},"ip_version":{"type":"integer","description":"IP protocol version (4 or 6)"},"hostname":{"type":"string","nullable":true,"description":"Reverse DNS hostname (null if no rDNS)"},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code"},"country_name":{"type":"string","description":"Human-readable country name"},"city":{"type":"string","nullable":true,"description":"City name"},"region":{"type":"string","nullable":true,"description":"Region or state"},"latitude":{"type":"number","nullable":true,"description":"Latitude coordinate"},"longitude":{"type":"number","nullable":true,"description":"Longitude coordinate"},"timezone":{"type":"string","nullable":true,"description":"IANA timezone identifier"},"asn":{"type":"integer","nullable":true,"description":"Autonomous System Number"},"isp":{"type":"string","nullable":true,"description":"Internet Service Provider"},"org":{"type":"string","nullable":true,"description":"Organization name"},"connection_type":{"type":"string","enum":["residential","mobile","business","datacenter","hosting","education","government","unknown"],"description":"Connection classification"}},"example":{"ip":"8.8.8.8","ip_version":4,"hostname":"dns.google","country":"US","country_name":"United States","city":"Mountain View","region":"California","latitude":37.386,"longitude":-122.0838,"timezone":"America/Los_Angeles","asn":15169,"isp":"Google LLC","org":"Google LLC","connection_type":"datacenter"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/{ip}/threat":{"get":{"tags":["IP Intelligence"],"summary":"IP threat assessment","description":"Returns threat score, level, categories, and detection flags.","operationId":"getIpThreat","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"1.2.3.4"}],"responses":{"200":{"description":"Threat score, categories, timeline, and detection flags","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"Queried IP address"},"score":{"type":"number","minimum":0,"maximum":100,"description":"Threat score (0 = clean, 100 = critical)"},"level":{"type":"string","enum":["clean","low","medium","high","critical"],"description":"Human-readable threat level derived from score"},"is_clean":{"type":"boolean","description":"True if no known threat reports"},"total_reports":{"type":"integer","description":"Number of abuse reports from all sources"},"categories":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Abuse categories (e.g. brute-force, port-scan, spam). Null if clean"},"first_seen":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of first abuse report"},"last_seen":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of most recent report"},"flags":{"type":"object","description":"Detection flags","properties":{"vpn":{"type":"boolean","description":"VPN endpoint"},"proxy":{"type":"boolean","description":"Proxy server"},"tor":{"type":"boolean","description":"Tor exit node"},"datacenter":{"type":"boolean","description":"Datacenter/cloud hosted"}}}},"example":{"ip":"203.0.113.50","score":75,"level":"high","is_clean":false,"total_reports":42,"categories":["brute-force","port-scan"],"first_seen":"2025-11-01T08:00:00Z","last_seen":"2026-03-29T14:22:00Z","flags":{"vpn":false,"proxy":true,"tor":false,"datacenter":true}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/threats/live":{"get":{"tags":["IP Intelligence"],"summary":"Live threat feed","description":"Returns the most recently reported threat IPs.","operationId":"getLiveThreats","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":25,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"Recently reported threat IPs sorted by recency","content":{"application/json":{"schema":{"type":"object","properties":{"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of feed generation"},"count":{"type":"integer","description":"Number of threat entries returned"},"threats":{"type":"array","description":"List of recently reported threat IPs","items":{"type":"object","properties":{"ip":{"type":"string","description":"Reported IP address"},"score":{"type":"number","description":"Threat score (0-100)"},"level":{"type":"string","description":"Threat level"},"categories":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Abuse categories"},"country":{"type":"string","nullable":true,"description":"ISO country code"},"last_seen":{"type":"string","nullable":true,"description":"Last report timestamp"}}}}},"example":{"updated_at":"2026-03-30T08:00:00Z","count":3,"threats":[{"ip":"203.0.113.50","score":85,"level":"high","categories":["brute-force"],"country":"CN","last_seen":"2026-03-30T07:55:00Z"},{"ip":"198.51.100.22","score":60,"level":"medium","categories":["port-scan"],"country":"RU","last_seen":"2026-03-30T07:50:00Z"},{"ip":"192.0.2.99","score":45,"level":"medium","categories":["spam"],"country":"BR","last_seen":"2026-03-30T07:48:00Z"}]}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/countries/{code}":{"get":{"tags":["IP Intelligence"],"summary":"Country intelligence","description":"Returns threat intelligence summary for a country.","operationId":"getCountryIntelligence","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"example":"NO","description":"ISO 3166-1 alpha-2 country code"}],"responses":{"200":{"description":"Threat intelligence aggregated by country","content":{"application/json":{"schema":{"type":"object","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code"},"country_name":{"type":"string","nullable":true,"description":"Human-readable country name"},"total_threats":{"type":"integer","description":"Total abuse reports originating from this country"},"threat_density":{"type":"number","nullable":true,"description":"Reports per unique IP (higher = more concentrated abuse)"},"top_categories":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Most common abuse categories from this country"}},"example":{"country":"NO","country_name":"Norway","total_threats":127,"threat_density":0.003,"top_categories":["brute-force","port-scan","spam"]}}}}},"404":{"description":"No data for country","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"No threat data available for this country"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/asn/{asn}":{"get":{"tags":["IP Intelligence"],"summary":"ASN intelligence","description":"Returns threat intelligence summary for an Autonomous System.","operationId":"getAsnIntelligence","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"asn","in":"path","required":true,"schema":{"type":"integer"},"example":15169,"description":"Autonomous System Number"}],"responses":{"200":{"description":"Threat intelligence aggregated by Autonomous System","content":{"application/json":{"schema":{"type":"object","properties":{"asn":{"type":"integer","description":"Autonomous System Number"},"name":{"type":"string","nullable":true,"description":"AS name (e.g. Google LLC)"},"total_ips":{"type":"integer","nullable":true,"description":"Unique IPs observed in this AS"},"threat_count":{"type":"integer","description":"Total abuse reports from this AS"},"threat_ratio":{"type":"number","nullable":true,"description":"Reports per unique IP (abuse concentration)"},"country":{"type":"string","nullable":true,"description":"Top country by report count"}},"example":{"asn":15169,"name":"Google LLC","total_ips":4200000,"threat_count":89,"threat_ratio":2e-05,"country":"US"}}}}},"404":{"description":"No data for ASN","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"No threat data available for this ASN"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/report":{"post":{"tags":["IP Intelligence"],"summary":"Submit IP abuse report","description":"Report an abusive IP address to the WAYSCloud threat intelligence network.\n\nRequires prior reporter registration via `POST /v1/ip/reporters/register`. Your WAYSCloud API key is used for authentication — the upstream reporter token is managed transparently.\n\n**Use cases:** fail2ban integration, honeypot data, IDS alerts, manual incident reports.\n\n**Rate limiting:** Reports count against your daily quota. Duplicate reports (same IP + category within 1 hour) are deduplicated.","operationId":"submitIpReport","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ip","category"],"properties":{"ip":{"type":"string","description":"IPv4 or IPv6 address to report as abusive","example":"157.180.31.156"},"category":{"type":"string","description":"Abuse category. Use descriptive names — categories are automatically normalized. Common values: ssh_bruteforce, port_scan, http_flood, sip_attack, rdp_bruteforce, web_attack, spam, phishing","example":"ssh_bruteforce"},"severity":{"type":"string","enum":["low","medium","high","critical"],"default":"medium","description":"Threat severity level. Affects how quickly the IP is flagged in the threat intelligence network"},"confidence":{"type":"number","minimum":0,"maximum":1,"default":1.0,"description":"How confident you are in this report (0.0 = uncertain, 1.0 = verified). Lower confidence reduces the impact on threat scoring"},"comment":{"type":"string","nullable":true,"description":"Free-text context about the abuse (e.g. \"Repeated SSH login attempts from this IP over 24h\")"},"session_id":{"type":"string","nullable":true,"description":"Unique session identifier for deduplication across retries. Format: tool-component-timestamp-unique (e.g. fail2ban-ssh-20260330-157180)"}},"example":{"ip":"45.155.205.233","category":"ssh_bruteforce","severity":"high","confidence":0.9,"comment":"Repeated SSH login attempts over 24h","session_id":"fail2ban-ssh-20260330-45155205233"}}}}},"responses":{"200":{"description":"Report accepted and submitted to the threat intelligence network","content":{"application/json":{"schema":{"type":"object","properties":{"report_id":{"type":"integer","description":"Unique numeric report identifier"},"oid_record":{"type":"string","description":"Norwegian NKOM Object Identifier for this report record"},"ip":{"type":"string","description":"The reported IP address"},"category":{"type":"string","description":"Normalized abuse category"},"initial_score":{"type":"number","nullable":true,"description":"Initial threat score assigned to the IP based on this report"},"message":{"type":"string","description":"Human-readable status message"}},"example":{"report_id":12345,"oid_record":"2.16.578.1.62.report.20260330.157180311156","ip":"157.180.31.156","category":"ssh_bruteforce","initial_score":24.0,"message":"Abuse report submitted successfully"}}}}},"403":{"description":"No reporter registration found. Register first via POST /v1/ip/reporters/register","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/reporters/register":{"post":{"tags":["IP Intelligence"],"summary":"Register as abuse reporter","description":"Register your service or organization as an abuse reporter in the WAYSCloud threat intelligence network.\n\nAfter registration, use `POST /v1/ip/report` to submit abuse reports using your regular WAYSCloud API key.\n\n**Trust scores** affect how much weight your reports carry:\n- `automated` (0.4): Fully automated systems like fail2ban or IDS\n- `hybrid` (0.6): Automated detection with human verification\n- `manual` (0.8): Human-verified reports\n- Verified domain (1.0): After DNS TXT verification via `POST /v1/ip/reporters/verify`\n\nEach WAYSCloud customer can have one active reporter registration.","operationId":"registerReporter","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name of your service or organization (e.g. \"ACME Mail Server\", \"Security Research Lab\")","example":"My Honeypot"},"source_type":{"type":"string","enum":["automated","manual","hybrid"],"default":"automated","description":"Reporter type — determines initial trust score. automated=fail2ban/IDS, manual=human review, hybrid=automated+human"},"contact":{"type":"string","nullable":true,"description":"Contact email for verification and support communication"},"intent":{"type":"string","nullable":true,"description":"What you plan to report (e.g. \"fail2ban SSH bruteforce\", \"honeypot data\", \"manual security analysis\")"},"domain":{"type":"string","nullable":true,"description":"Your domain for ownership verification. Add DNS TXT record later to increase trust score to 1.0"}},"example":{"name":"ACME Security Lab","source_type":"hybrid","contact":"security@acme.com","intent":"fail2ban + manual incident review","domain":"acme.com"}}}}},"responses":{"201":{"description":"Reporter successfully registered","content":{"application/json":{"schema":{"type":"object","properties":{"reporter_id":{"type":"string","description":"Unique reporter UUID — used for domain verification"},"reporter_token":{"type":"string","description":"Upstream reporter token (managed internally — you authenticate with your WAYSCloud API key)"},"oid":{"type":"string","nullable":true,"description":"Norwegian NKOM Object Identifier for this reporter"},"trust_score":{"type":"number","description":"Initial trust score (0.4 automated, 0.6 hybrid, 0.8 manual)"},"status":{"type":"string","description":"Reporter status (active)"},"usage":{"type":"object","description":"Instructions for submitting reports"}},"example":{"reporter_id":"550e8400-e29b-41d4-a716-446655440000","reporter_token":"wayscloud_abc123...","oid":"2.16.578.1.62.reporter.550e8400","trust_score":0.4,"status":"active","usage":{"report_endpoint":"POST /v1/ip/report","auth":"Use your WAYSCloud API key (same X-API-Key header)"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/reporters/verify":{"post":{"tags":["IP Intelligence"],"summary":"Verify reporter domain ownership","description":"Verify that you own the domain associated with your reporter registration. This increases your trust score to 1.0 (maximum), meaning your reports carry the highest weight.\n\n**Steps:**\n1. Register with a domain via `POST /v1/ip/reporters/register`\n2. Add a DNS TXT record: `wayscloud-verify=<reporter_id>` to your domain (or `_wayscloud.yourdomain.com`)\n3. Call this endpoint to trigger verification\n\n**DNS propagation** may take 5–15 minutes after adding the TXT record.","operationId":"verifyReporter","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reporter_id":{"type":"string","nullable":true,"description":"Reporter UUID to verify (from registration response)"},"domain":{"type":"string","nullable":true,"description":"Domain to verify (alternative to reporter_id — looks up the reporter by domain)"}},"example":{"reporter_id":"550e8400-e29b-41d4-a716-446655440000","domain":"acme.com"}}}}},"responses":{"200":{"description":"Domain ownership verified — trust score upgraded to 1.0","content":{"application/json":{"schema":{"type":"object","properties":{"reporter_id":{"type":"string","description":"Verified reporter UUID"},"domain":{"type":"string","description":"Verified domain"},"trust_score":{"type":"number","description":"Updated trust score (1.0 after verification)"},"message":{"type":"string","description":"Verification status message"}},"example":{"reporter_id":"550e8400-e29b-41d4-a716-446655440000","domain":"acme.com","trust_score":1.0,"message":"Domain ownership verified successfully"}}}}},"400":{"description":"DNS TXT record not found — add wayscloud-verify=<reporter_id> to your domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Reporter not found or domain not registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/delist":{"post":{"tags":["IP Intelligence"],"summary":"Request IP delisting","description":"Request removal or review of an IP address from WAYSCloud threat lists.\n\n**Dual verification required:**\n1. The request should ideally originate from the IP being delisted (IP verification)\n2. You must provide a hostname (e.g. mail.example.com) that resolves to the IP\n3. After submitting, add a DNS TXT record: `wayscloud-delist=<request_id>` on the hostname\n\n**Common use cases:**\n- Server was compromised but is now secured\n- Inherited a \"dirty\" IP from previous owner\n- False positive / misclassification\n- Shared hosting or VPN service\n\n**Timeline:** Requests are reviewed within 24–48 hours after DNS verification.","operationId":"requestDelist","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ip_address","hostname","reason","contact_email"],"properties":{"ip_address":{"type":"string","description":"IPv4 or IPv6 address to request delisting for","example":"157.180.31.156"},"hostname":{"type":"string","description":"Hostname that resolves to this IP (e.g. mail.example.com). Used for DNS-based ownership verification","example":"mail.example.com"},"reason":{"type":"string","minLength":20,"description":"Detailed reason for the delist request (minimum 20 characters). Explain what happened and what remediation was done","example":"Server was compromised via outdated WordPress plugin. Now fully patched, firewall updated, and malware removed."},"contact_email":{"type":"string","format":"email","description":"Email address for follow-up communication about the delist request","example":"admin@example.com"}},"example":{"ip_address":"157.180.31.156","hostname":"mail.example.com","reason":"Server was compromised via outdated WordPress plugin. Now fully patched, firewall updated, and malware removed.","contact_email":"admin@example.com"}}}}},"responses":{"201":{"description":"Delist request submitted — follow DNS verification instructions in response","content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","description":"Unique request UUID — use this in your DNS TXT record for verification"},"ip_address":{"type":"string","description":"The IP address in the delist request"},"hostname":{"type":"string","description":"The hostname provided for DNS verification"},"verification_status":{"type":"string","nullable":true,"description":"Current verification state (ip_verified, dns_pending, fully_verified)"},"dns_verification":{"type":"object","nullable":true,"description":"DNS TXT record instructions for completing verification"},"message":{"type":"string","description":"Next steps for completing the delist process"}},"example":{"request_id":"550e8400-e29b-41d4-a716-446655440000","ip_address":"157.180.31.156","hostname":"mail.example.com","verification_status":"ip_verified","dns_verification":{"txt_record":"wayscloud-delist=550e8400-e29b-41d4-a716-446655440000","instructions":["Add TXT record to mail.example.com","Test with: dig TXT mail.example.com +short"]},"message":"Delist request submitted. Add DNS TXT record to complete verification."}}}}},"400":{"description":"Invalid request — missing hostname or invalid format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"IP mismatch — request must originate from the target IP address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}}},"components":{"schemas":{"GPUEngine":{"type":"object","properties":{"engine":{"type":"string","description":"Engine identifier"},"name":{"type":"string","description":"Display name"},"description":{"type":"string"},"type":{"type":"string","enum":["image","video"]},"tier_required":{"type":"string","nullable":true,"description":"Required tier (null = basic)"},"available":{"type":"boolean","description":"Currently available"}},"example":{"engine":"image-flux-pro","name":"FLUX Pro","description":"High-quality images with fine details","type":"image","tier_required":"pro","available":true}},"GPUEngineList":{"type":"array","items":{"$ref":"#/components/schemas/GPUEngine"},"example":[{"engine":"image-flux-fast","name":"FLUX Fast","description":"Quick image drafts","type":"image","tier_required":null,"available":true},{"engine":"image-flux-pro","name":"FLUX Pro","description":"High-quality images","type":"image","tier_required":"pro","available":true},{"engine":"video-veo-pro","name":"Veo Pro","description":"8-sec 1080p video","type":"video","tier_required":"pro","available":true}]},"GPUPreset":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["image","video"]},"description":{"type":"string","nullable":true}},"example":{"name":"cinematic_light","type":"image","description":"Cinematic lighting style"}},"GPUPricing":{"type":"object","properties":{"engine":{"type":"string"},"sku":{"type":"string"},"type":{"type":"string","enum":["image","video"]},"unit":{"type":"string","enum":["image","seconds"]},"price_per_unit":{"type":"number"},"currency":{"type":"string"},"display_name":{"type":"string"},"description":{"type":"string","nullable":true},"tier_required":{"type":"string","nullable":true}},"example":{"engine":"image-flux-fast","sku":"gpu_image_flux_fast","type":"image","unit":"image","price_per_unit":2.0,"currency":"NOK","display_name":"FLUX Fast","description":"Quick image generation","tier_required":null}},"GPUJobCreate":{"type":"object","required":["type","engine","inputs"],"properties":{"type":{"type":"string","enum":["image","video"]},"engine":{"type":"string","description":"Engine to use"},"inputs":{"type":"object","properties":{"prompt":{"type":"string","description":"Description of what to generate"},"aspect_ratio":{"type":"string","enum":["1:1","4:5","16:9","9:16"],"default":"16:9"},"duration_sec":{"type":"integer","description":"Video duration (video only)"},"negative_prompt":{"type":"string","description":"What to avoid"},"seed":{"type":"integer","description":"Random seed"}},"required":["prompt"]},"preset":{"type":"string","nullable":true,"description":"Style preset"},"webhook_url":{"type":"string","format":"uri","nullable":true,"description":"Callback URL (video only)"}},"example":{"type":"image","engine":"image-flux-fast","inputs":{"prompt":"A serene mountain landscape at sunset","aspect_ratio":"16:9"}}},"GPUJobCost":{"type":"object","properties":{"amount":{"type":"number"},"currency":{"type":"string"}},"example":{"amount":2.0,"currency":"NOK"}},"GPUJob":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["image","video"]},"engine":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]},"progress":{"type":"integer","minimum":0,"maximum":100},"output_files":{"type":"object","nullable":true,"description":"URLs to generated files"},"estimated_cost":{"$ref":"#/components/schemas/GPUJobCost","nullable":true},"actual_cost":{"$ref":"#/components/schemas/GPUJobCost","nullable":true},"error_message":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","type":"image","engine":"image-flux-fast","status":"completed","progress":100,"output_files":{"image":"https://storage.wayscloud.services/gpu-studio/tenant/<tenant_id>/jobs/<job_id>/output.png"},"estimated_cost":{"amount":2.0,"currency":"NOK"},"actual_cost":{"amount":2.0,"currency":"NOK"},"error_message":null,"created_at":"2025-01-15T10:30:00Z","completed_at":"2025-01-15T10:30:12Z"}},"TranscriptJobCreate":{"type":"object","required":["filename","content_type","file_size_bytes"],"properties":{"filename":{"type":"string","maxLength":255,"description":"Original filename"},"content_type":{"type":"string","maxLength":100,"description":"MIME type (e.g., audio/mpeg, audio/wav)"},"file_size_bytes":{"type":"integer","minimum":1,"maximum":2147483648,"description":"File size in bytes (max 2 GB)"},"language":{"type":"string","maxLength":10,"default":"auto","description":"ISO 639-1 language code or \"auto\" for detection"}},"example":{"filename":"meeting-recording.mp3","content_type":"audio/mpeg","file_size_bytes":15728640,"language":"auto"}},"TranscriptJobCreateResponse":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"upload_url":{"type":"string","format":"uri","description":"Presigned S3 upload URL"},"upload_expires_in":{"type":"integer","description":"Upload URL expiry in seconds"}},"example":{"job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","upload_url":"https://storage.wayscloud.services/transcript-originals/...","upload_expires_in":3600}},"TranscriptSegment":{"type":"object","properties":{"segment_index":{"type":"integer"},"start_time":{"type":"number","description":"Start time in seconds"},"end_time":{"type":"number","description":"End time in seconds"},"text":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1,"nullable":true}}},"TranscriptArtifact":{"type":"object","properties":{"artifact_id":{"type":"string","format":"uuid"},"format":{"type":"string","enum":["txt","json","srt","vtt"]},"download_url":{"type":"string","format":"uri"},"size_bytes":{"type":"integer"}},"example":{"artifact_id":"c3d4e5f6-7890-1234-abcd-ef5678901234","format":"srt","download_url":"https://api.wayscloud.services/v1/transcript/artifacts/c3d4e5f6-7890-1234-abcd-ef5678901234/download","size_bytes":4820}},"TranscriptJob":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["created","queued","processing","ready","failed","expired"]},"language":{"type":"string","nullable":true,"description":"Detected or specified language code"},"original_filename":{"type":"string"},"audio_duration_sec":{"type":"number","nullable":true,"description":"Audio duration in seconds (available after processing)"},"processing_time_ms":{"type":"integer","nullable":true,"description":"Total processing time in milliseconds"},"error_message":{"type":"string","nullable":true,"description":"Error details (only when status is failed)"},"segments":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptSegment"},"nullable":true,"description":"Transcription segments (only included when status is ready, on detail endpoint)"},"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptArtifact"},"nullable":true,"description":"Available export artifacts with download URLs (only included when status is ready, on detail endpoint)"},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"example":{"job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","status":"ready","language":"no","original_filename":"meeting-recording.mp3","audio_duration_sec":1847.3,"processing_time_ms":42150,"error_message":null,"created_at":"2025-01-15T10:30:00Z","completed_at":"2025-01-15T10:30:42Z"}},"TranscriptExportRequest":{"type":"object","required":["format"],"properties":{"format":{"type":"string","enum":["txt","json","srt","vtt"],"description":"Export format"}},"example":{"format":"srt"}},"SendSMSRequest":{"type":"object","required":["message"],"properties":{"to":{"type":"array","items":{"type":"string"},"maxItems":1000,"description":"List of recipient phone numbers (E.164 format)"},"to_contacts":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":1000,"description":"List of contact UUIDs to send to"},"to_groups":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"description":"List of contact group UUIDs to send to"},"message":{"type":"string","minLength":1,"maxLength":1600,"description":"Message content"},"sender_profile_id":{"type":"string","description":"Sender profile ID (uses default if not specified)"},"allow_reply":{"type":"boolean","default":false,"description":"Allow recipient to reply"},"customer_ref":{"type":"string","maxLength":255,"description":"Your reference ID for correlation"},"callback_url":{"type":"string","format":"uri","description":"Webhook URL for delivery status (HTTPS required)"},"scheduled_at":{"type":"string","format":"date-time","description":"Schedule for future delivery (ISO 8601). Max 30 days ahead. Use timezone param for local time."},"timezone":{"type":"string","description":"IANA timezone for scheduled_at (e.g., Europe/Oslo, Europe/Stockholm). If omitted, scheduled_at is treated as UTC.","example":"Europe/Oslo"}},"example":{"to":["+4799999999"],"message":"Reminder: Your appointment is tomorrow at 10:00","scheduled_at":"2025-12-17T09:00:00","timezone":"Europe/Oslo","customer_ref":"reminder-12345"}},"SendSMSResponse":{"type":"object","properties":{"sms_message_id":{"type":"string","format":"uuid","description":"Unique message ID"},"to":{"type":"array","items":{"type":"string"},"description":"Recipient numbers"},"status":{"type":"string","enum":["QUEUED","SCHEDULED","SENDING","SENT","DELIVERED","FAILED"],"description":"Message status"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"units_estimate":{"type":"integer","description":"Estimated SMS units"},"customer_ref":{"type":"string","nullable":true}},"example":{"sms_message_id":"550e8400-e29b-41d4-a716-446655440000","to":["+4799999999"],"status":"QUEUED","units_estimate":1,"customer_ref":"order-12345"}},"SMSMessage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"direction":{"type":"string","enum":["OUTBOUND","INBOUND"]},"message_type":{"type":"string","enum":["PLAIN","REPLY","VERIFY"]},"from_number":{"type":"string","nullable":true},"to_number":{"type":"string"},"body":{"type":"string"},"status":{"type":"string","enum":["QUEUED","SCHEDULED","SENDING","SENT","DELIVERED","FAILED","CANCELLED","RECEIVED"]},"units_count":{"type":"integer","nullable":true},"customer_ref":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"delivered_at":{"type":"string","format":"date-time","nullable":true},"related_message_id":{"type":"string","format":"uuid","nullable":true},"error_message":{"type":"string","nullable":true}},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","direction":"OUTBOUND","message_type":"PLAIN","from_number":null,"to_number":"+4799999999","body":"Your order #1234 has shipped.","status":"DELIVERED","units_count":1,"customer_ref":"order-1234","created_at":"2026-03-30T10:00:00Z","sent_at":"2026-03-30T10:00:01Z","delivered_at":"2026-03-30T10:00:03Z","error_message":null}},"SMSMessageList":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/SMSMessage"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"},"has_more":{"type":"boolean"}},"example":{"messages":[{"id":"550e8400-e29b-41d4-a716-446655440000","direction":"OUTBOUND","to_number":"+4799999999","body":"Your order #1234 has shipped.","status":"DELIVERED","units_count":1,"created_at":"2026-03-30T10:00:00Z"},{"id":"660f9511-f3a0-5483-b827-1f13c4d55111","direction":"OUTBOUND","to_number":"+4798765432","body":"Meeting at 14:00 today.","status":"SENT","units_count":1,"created_at":"2026-03-30T09:30:00Z"}],"total":47,"page":1,"page_size":50,"has_more":false}},"SenderProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"sender_id":{"type":"string","description":"Phone number or alphanumeric (max 11 chars)"},"is_default":{"type":"boolean"},"allow_reply":{"type":"boolean"},"is_custom_sender":{"type":"boolean"},"approval_status":{"type":"string","enum":["pending","approved","rejected"]},"monthly_price":{"type":"number","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"marketing","sender_id":"MyBrand","is_default":false,"allow_reply":false,"is_custom_sender":true,"approval_status":"approved","monthly_price":99.0,"created_at":"2025-01-15T10:00:00Z"}},"CreateSenderProfileRequest":{"type":"object","required":["name","sender_id"],"properties":{"name":{"type":"string","minLength":1,"maxLength":50,"description":"Profile name"},"sender_id":{"type":"string","minLength":1,"maxLength":15,"description":"Sender ID (phone or alphanumeric max 11 chars, no ÆØÅ)"},"allow_reply":{"type":"boolean","default":true},"is_default":{"type":"boolean","default":false},"brand_confirmation":{"type":"boolean","default":false,"description":"Required for custom sender IDs"}},"example":{"name":"marketing","sender_id":"MyBrand","allow_reply":false,"brand_confirmation":true}},"SMSServiceStatus":{"type":"object","properties":{"active":{"type":"boolean"},"has_sender_profiles":{"type":"boolean"},"has_api_key":{"type":"boolean"},"default_sender":{"$ref":"#/components/schemas/SenderProfile","nullable":true},"messages_today":{"type":"integer"},"messages_this_month":{"type":"integer"},"messages_sent_30d":{"type":"integer"},"messages_received_30d":{"type":"integer"}},"example":{"active":true,"has_sender_profiles":true,"has_api_key":true,"default_sender":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"default","sender_id":"4799999999","is_default":true},"messages_today":12,"messages_this_month":347,"messages_sent_30d":892,"messages_received_30d":45}},"SMSUsageResponse":{"type":"object","description":"SMS usage statistics for a billing period","properties":{"period":{"type":"string","description":"Billing period (YYYY-MM)","example":"2025-12"},"period_start":{"type":"string","format":"date","example":"2025-12-01"},"period_end":{"type":"string","format":"date","example":"2025-12-31"},"messages_sent":{"type":"integer","description":"Total outbound messages","example":150},"messages_received":{"type":"integer","description":"Total inbound messages","example":23},"messages_failed":{"type":"integer","description":"Failed messages","example":2},"units_used":{"type":"integer","description":"SMS units consumed","example":167},"cost":{"type":"number","format":"float","description":"Total cost","example":165.33},"currency":{"type":"string","description":"Currency code","example":"NOK"},"price_per_unit":{"type":"number","format":"float","description":"Price per SMS unit","example":0.99},"outbound_cost":{"type":"number","format":"float","description":"Cost for outbound messages","example":165.33},"inbound_cost":{"type":"number","format":"float","description":"Cost for inbound messages (usually 0)","example":0.0}},"example":{"period":"2025-12","period_start":"2025-12-01","period_end":"2025-12-31","messages_sent":150,"messages_received":23,"messages_failed":2,"units_used":167,"cost":165.33,"currency":"NOK","price_per_unit":0.99,"outbound_cost":165.33,"inbound_cost":0.0}},"SMSStatsPeriod":{"type":"object","description":"Statistics for a specific time period","properties":{"messages_sent":{"type":"integer","description":"Outbound messages"},"messages_received":{"type":"integer","description":"Inbound messages"},"units_used":{"type":"integer","description":"SMS units consumed"},"delivery_rate":{"type":"number","format":"float","description":"Delivery success rate (0-1)","example":0.95}}},"SMSStatsResponse":{"type":"object","description":"Aggregated SMS statistics across time periods","properties":{"today":{"$ref":"#/components/schemas/SMSStatsPeriod"},"this_week":{"$ref":"#/components/schemas/SMSStatsPeriod"},"this_month":{"$ref":"#/components/schemas/SMSStatsPeriod"},"last_30_days":{"$ref":"#/components/schemas/SMSStatsPeriod"},"status_breakdown":{"type":"object","description":"Message count by status","additionalProperties":{"type":"integer"},"example":{"QUEUED":5,"SENT":10,"DELIVERED":130,"FAILED":5}}},"example":{"today":{"messages_sent":12,"messages_received":3,"units_used":14,"delivery_rate":0.92},"this_week":{"messages_sent":45,"messages_received":8,"units_used":52,"delivery_rate":0.95},"this_month":{"messages_sent":150,"messages_received":23,"units_used":167,"delivery_rate":0.98},"last_30_days":{"messages_sent":180,"messages_received":28,"units_used":201,"delivery_rate":0.97},"status_breakdown":{"QUEUED":5,"SENT":10,"DELIVERED":130,"FAILED":5}}},"SMSKeyword":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"keyword":{"type":"string"},"description":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"webhook_url":{"type":"string","nullable":true},"auto_reply_enabled":{"type":"boolean"},"auto_reply_message":{"type":"string","nullable":true},"messages_received":{"type":"integer"},"last_message_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","keyword":"SUPPORT","description":"Customer support keyword","is_active":true,"webhook_url":"https://example.com/webhooks/sms","auto_reply_enabled":true,"auto_reply_message":"Thank you for contacting support. We will respond shortly.","messages_received":42,"created_at":"2025-01-15T10:00:00Z"}},"CreateKeywordRequest":{"type":"object","required":["keyword"],"properties":{"keyword":{"type":"string","minLength":1,"maxLength":20,"description":"Keyword (alphanumeric, case-insensitive)"},"description":{"type":"string","maxLength":255},"webhook_url":{"type":"string","format":"uri"},"auto_reply_message":{"type":"string","maxLength":1600}},"example":{"keyword":"SUPPORT","description":"Customer support keyword","webhook_url":"https://example.com/webhooks/sms","auto_reply_message":"Thank you for contacting support."}},"UpdateKeywordRequest":{"type":"object","properties":{"description":{"type":"string","maxLength":255},"webhook_url":{"type":"string","format":"uri","nullable":true},"auto_reply_enabled":{"type":"boolean"},"auto_reply_message":{"type":"string","maxLength":1600,"nullable":true},"is_active":{"type":"boolean"}},"example":{"description":"Customer support keyword","webhook_url":"https://example.com/webhooks/sms","auto_reply_enabled":true,"auto_reply_message":"Thank you for contacting support. We will respond shortly."}},"AccountProfile":{"type":"object","properties":{"customer_id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"customer_type":{"type":"string","enum":["private","business"]},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"example":{"customer_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","email":"kristen.nygaard@simula.no","first_name":"Kristen","last_name":"Nygaard","company":"Simula Research Laboratory","customer_type":"business","status":"active","created_at":"2024-01-15T09:30:00Z"}},"APIKeyItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key_prefix":{"type":"string"},"key_type":{"type":"string","enum":["service","account"]},"service":{"type":"string"},"description":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"}},"example":{"id":"a8b9c0d1-e2f3-4567-89ab-cdef01234567","key_prefix":"wayscloud_2xK9mPq","key_type":"service","service":"database","description":"Production database access","scopes":["database:read","database:write"],"is_active":true,"created_at":"2024-03-01T14:00:00Z","last_used_at":"2024-03-15T08:45:22Z"}},"APIKeyList":{"type":"array","items":{"$ref":"#/components/schemas/APIKeyItem"},"example":[{"id":"a8b9c0d1-e2f3-4567-89ab-cdef01234567","key_prefix":"wayscloud_2xK9mPq","key_type":"service","service":"database","is_active":true},{"id":"b9c0d1e2-f3a4-5678-9abc-def012345678","key_prefix":"wayscloud_pat_7Zk","key_type":"account","service":null,"is_active":true}]},"CreateServiceKeyRequest":{"type":"object","required":["service"],"properties":{"service":{"type":"string","enum":["database","storage","llm-api","dns","vps"]},"description":{"type":"string"}},"example":{"service":"database","description":"CI/CD pipeline database access"}},"CreatePATRequest":{"type":"object","required":["scopes"],"properties":{"scopes":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"expires_in_days":{"type":"integer","minimum":1,"maximum":365}},"example":{"scopes":["api_keys:write","ssh_keys:read","services:read"],"description":"Terraform automation token","expires_in_days":90}},"APIKeyCreated":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","description":"Full key - shown only once"},"key_prefix":{"type":"string"},"key_type":{"type":"string"},"warning":{"type":"string"}},"example":{"id":"c0d1e2f3-a4b5-6789-abcd-ef0123456789","key":"wayscloud_3mN8pQrStUvWxYz_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789","key_prefix":"wayscloud_3mN8pQr","key_type":"service","warning":"Save this key now. It cannot be retrieved later."}},"SSHKeyItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"fingerprint":{"type":"string"},"key_type":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"vm_count":{"type":"integer"}},"example":{"id":"d1e2f3a4-b5c6-7890-abcd-ef1234567890","name":"deploy-server","fingerprint":"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8","key_type":"ssh-ed25519","created_at":"2024-02-20T11:15:00Z","vm_count":3}},"SSHKeyList":{"type":"array","items":{"$ref":"#/components/schemas/SSHKeyItem"},"example":[{"id":"d1e2f3a4-b5c6-7890-abcd-ef1234567890","name":"deploy-server","fingerprint":"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8","key_type":"ssh-ed25519","vm_count":3},{"id":"e2f3a4b5-c6d7-8901-bcde-f23456789012","name":"github-actions","fingerprint":"SHA256:kR9H2mK5pL3nQ8vX1cB4jT6wY0zA7sD2fG5hJ8kL9mN","key_type":"ssh-rsa","vm_count":1}]},"CreateSSHKeyRequest":{"type":"object","required":["name","public_key"],"properties":{"name":{"type":"string"},"public_key":{"type":"string"}},"example":{"name":"deploy-server","public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl user@workstation"}},"ServiceItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"service_type":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"region":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"metadata":{"type":"object"}},"example":{"id":"f3a4b5c6-d7e8-9012-cdef-345678901234","service_type":"database","name":"webapp-prod-db","status":"running","region":"oslo","created_at":"2024-02-01T10:00:00Z","metadata":{"engine":"postgresql","version":"16"}}},"ServiceList":{"type":"array","items":{"$ref":"#/components/schemas/ServiceItem"},"example":[{"id":"f3a4b5c6-d7e8-9012-cdef-345678901234","service_type":"database","name":"webapp-prod-db","status":"running","region":"oslo"},{"id":"a4b5c6d7-e8f9-0123-def4-567890123456","service_type":"storage","name":"company-assets","status":"active","region":"oslo"}]},"DeleteSuccess":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"Resource deleted successfully"}},"Error":{"type":"object","properties":{"detail":{"type":"string"}},"example":{"detail":"Resource not found"}},"DynNameCreateRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$","description":"Single dynway.eu label, for example `home` for `home.dynway.eu`.","example":"home"}},"example":{"name":"home"}},"DynNameAvailabilityResponse":{"type":"object","required":["name","fqdn","available"],"properties":{"name":{"type":"string","example":"home"},"fqdn":{"type":"string","example":"home.dynway.eu"},"available":{"type":"boolean","description":"Whether no live Dynway name currently holds this label.","example":true}},"example":{"name":"home","fqdn":"home.dynway.eu","available":true}},"DynNameMetadata":{"type":"object","required":["tags","expected_interval_seconds","offline_after_seconds","monitoring_enabled"],"properties":{"display_name":{"type":"string","nullable":true,"maxLength":120,"example":"Oslo gateway"},"site_code":{"type":"string","nullable":true,"maxLength":64,"example":"OSL-01"},"location_label":{"type":"string","nullable":true,"maxLength":120,"example":"Oslo"},"tags":{"type":"array","maxItems":20,"items":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9._:-]{0,63})$"},"example":["oslo","vpn"]},"expected_interval_seconds":{"type":"integer","minimum":60,"maximum":604800,"example":900},"offline_after_seconds":{"type":"integer","minimum":60,"maximum":2592000,"example":1800},"monitoring_enabled":{"type":"boolean","example":true},"updated_at":{"type":"string","format":"date-time","nullable":true}},"example":{"display_name":"Oslo gateway","site_code":"OSL-01","location_label":"Oslo","tags":["oslo","vpn"],"expected_interval_seconds":900,"offline_after_seconds":1800,"monitoring_enabled":true,"updated_at":"2026-08-25T13:20:27Z"}},"DynNameMetadataUpdateRequest":{"type":"object","additionalProperties":false,"properties":{"display_name":{"type":"string","nullable":true,"maxLength":120},"site_code":{"type":"string","nullable":true,"maxLength":64},"location_label":{"type":"string","nullable":true,"maxLength":120},"tags":{"type":"array","nullable":true,"maxItems":20,"items":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9._:-]{0,63})$"}},"expected_interval_seconds":{"type":"integer","minimum":60,"maximum":604800},"offline_after_seconds":{"type":"integer","minimum":60,"maximum":2592000},"monitoring_enabled":{"type":"boolean"}},"example":{"display_name":"Oslo gateway","tags":["oslo","vpn"],"expected_interval_seconds":900,"offline_after_seconds":1800}},"DynName":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"home"},"fqdn":{"type":"string","example":"home.dynway.eu"},"current_ip":{"type":"string","format":"ipv4","nullable":true,"deprecated":true,"description":"Legacy IPv4 address field. Use current_ipv4/current_ipv6 for dual-stack state.","example":"203.0.113.10"},"current_ipv4":{"type":"string","format":"ipv4","nullable":true,"description":"Current public IPv4 address used for the A record.","example":"203.0.113.10"},"current_ipv6":{"type":"string","format":"ipv6","nullable":true,"description":"Current public IPv6 address used for the AAAA record.","example":"2001:4860:4860::8888"},"last_update_at":{"type":"string","format":"date-time","nullable":true},"last_seen_at":{"type":"string","format":"date-time","nullable":true},"seen_count":{"type":"integer","example":12},"status":{"type":"string","nullable":true,"enum":["unknown","online","late","offline","auth_error","abuse_limited","server_error","dns_drift"],"description":"Current materialized status when the Business Fleet schema is available.","example":"online"},"dns_drift_detected":{"type":"boolean","description":"Whether the most recent authoritative DNS observation differed from Dynway state.","example":false},"dns_checked_at":{"type":"string","format":"date-time","nullable":true,"description":"Most recent successful authoritative DNS drift check."},"metadata":{"allOf":[{"$ref":"#/components/schemas/DynNameMetadata"}],"nullable":true,"description":"Business Fleet labels, tags, and monitoring settings. Null for Home Free."},"dns_removal_pending":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time"},"active_token_count":{"type":"integer","example":1},"active_token_ids":{"type":"array","items":{"type":"string"},"example":["dyn7f3a91c4"]}},"example":{"id":"22222222-2222-2222-2222-222222222222","name":"home","fqdn":"home.dynway.eu","current_ip":"203.0.113.10","current_ipv4":"203.0.113.10","current_ipv6":"2001:4860:4860::8888","last_update_at":"2026-08-25T10:15:30Z","last_seen_at":"2026-08-25T10:15:30Z","seen_count":12,"status":"online","dns_drift_detected":false,"dns_checked_at":"2026-08-25T13:25:00Z","metadata":{"display_name":"Oslo gateway","site_code":"OSL-01","location_label":"Oslo","tags":["oslo","vpn"],"expected_interval_seconds":900,"offline_after_seconds":1800,"monitoring_enabled":true,"updated_at":"2026-08-25T10:15:30Z"},"dns_removal_pending":false,"created_at":"2026-08-25T09:00:00Z","active_token_count":1,"active_token_ids":["dyn7f3a91c4"]}},"DynNameListResponse":{"type":"object","properties":{"names":{"type":"array","items":{"$ref":"#/components/schemas/DynName"}},"quota":{"type":"integer","description":"Plan-specific live-name limit.","example":1},"used":{"type":"integer","example":1},"plan":{"type":"string","enum":["home_free","business_fleet"],"example":"home_free"},"enabled":{"type":"boolean","description":"Whether new Dynway names may be created for this account.","example":true}},"example":{"names":[{"id":"22222222-2222-2222-2222-222222222222","name":"home","fqdn":"home.dynway.eu","current_ip":"203.0.113.10","current_ipv4":"203.0.113.10","current_ipv6":"2001:4860:4860::8888","last_update_at":"2026-08-25T10:15:30Z","last_seen_at":"2026-08-25T10:15:30Z","seen_count":12,"dns_removal_pending":false,"created_at":"2026-08-25T09:00:00Z","active_token_count":1,"active_token_ids":["dyn7f3a91c4"]}],"quota":1,"used":1,"plan":"home_free","enabled":true}},"DynEntitlementResponse":{"type":"object","required":["plan","customer_type","enabled","name_quota","active_tokens_per_name"],"properties":{"plan":{"type":"string","enum":["home_free","business_fleet"],"example":"business_fleet"},"customer_type":{"type":"string","example":"business"},"enabled":{"type":"boolean","example":true},"disabled_reason":{"type":"string","nullable":true,"example":null},"name_quota":{"type":"integer","example":50},"active_tokens_per_name":{"type":"integer","example":1}},"example":{"plan":"business_fleet","customer_type":"business","enabled":true,"disabled_reason":null,"name_quota":50,"active_tokens_per_name":1}},"DynNameStatusResponse":{"type":"object","required":["name","fqdn","status","dns_drift_detected","monitoring_enabled"],"properties":{"name":{"type":"string","example":"oslo-gw"},"fqdn":{"type":"string","example":"oslo-gw.dynway.eu"},"status":{"type":"string","enum":["unknown","online","late","offline","auth_error","abuse_limited","server_error","dns_drift"],"example":"online"},"status_since":{"type":"string","format":"date-time","nullable":true},"current_ipv4":{"type":"string","format":"ipv4","nullable":true},"current_ipv6":{"type":"string","format":"ipv6","nullable":true},"last_update_at":{"type":"string","format":"date-time","nullable":true},"last_seen_at":{"type":"string","format":"date-time","nullable":true},"last_good_at":{"type":"string","format":"date-time","nullable":true},"last_nochg_at":{"type":"string","format":"date-time","nullable":true},"last_result":{"type":"string","nullable":true,"example":"nochg"},"last_error_at":{"type":"string","format":"date-time","nullable":true},"last_error_code":{"type":"string","nullable":true,"example":"badauth"},"dns_drift_detected":{"type":"boolean","example":false},"dns_checked_at":{"type":"string","format":"date-time","nullable":true,"description":"Most recent successful authoritative DNS drift check."},"ipv4_status":{"type":"string","nullable":true,"example":"good"},"ipv6_status":{"type":"string","nullable":true,"example":"nochg"},"monitoring_enabled":{"type":"boolean","description":"Business Fleet monitoring setting. Always false for Home Free.","example":true},"expected_interval_seconds":{"type":"integer","nullable":true,"description":"Business Fleet expected check-in interval.","example":900},"offline_after_seconds":{"type":"integer","nullable":true,"description":"Business Fleet offline threshold.","example":1800},"next_expected_by":{"type":"string","format":"date-time","nullable":true,"description":"Next expected router check-in for monitored Business Fleet names."}},"example":{"name":"oslo-gw","fqdn":"oslo-gw.dynway.eu","status":"online","status_since":"2026-08-25T13:20:27Z","current_ipv4":"203.0.113.10","current_ipv6":"2001:db8::10","last_update_at":"2026-08-25T13:20:27Z","last_seen_at":"2026-08-25T13:20:27Z","last_good_at":"2026-08-25T12:05:11Z","last_nochg_at":"2026-08-25T13:20:27Z","last_result":"nochg","last_error_at":null,"last_error_code":null,"dns_drift_detected":false,"dns_checked_at":"2026-08-25T13:25:00Z","ipv4_status":"good","ipv6_status":"nochg","monitoring_enabled":true,"expected_interval_seconds":900,"offline_after_seconds":1800,"next_expected_by":"2026-08-25T13:35:27Z"}},"DynEventResponse":{"type":"object","required":["id","name","fqdn","event_type","result","detail","occurred_at","received_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"oslo-gw"},"fqdn":{"type":"string","example":"oslo-gw.dynway.eu"},"event_type":{"type":"string","example":"dyn.update.nochg"},"result":{"type":"string","example":"nochg"},"address_family":{"type":"string","enum":["ipv4","ipv6"],"nullable":true},"current_ipv4":{"type":"string","format":"ipv4","nullable":true},"current_ipv6":{"type":"string","format":"ipv6","nullable":true},"previous_ipv4":{"type":"string","format":"ipv4","nullable":true},"previous_ipv6":{"type":"string","format":"ipv6","nullable":true},"token_id":{"type":"string","nullable":true,"description":"Router token identifier only; never the router password."},"detail":{"type":"object","description":"Sanitized status-transition detail. Router passwords, ingestion, stream, router fingerprint, and other credential data are omitted.","additionalProperties":true},"occurred_at":{"type":"string","format":"date-time"},"received_at":{"type":"string","format":"date-time"}},"example":{"id":"33333333-3333-3333-3333-333333333333","name":"oslo-gw","fqdn":"oslo-gw.dynway.eu","event_type":"dyn.update.nochg","result":"nochg","address_family":"ipv6","current_ipv4":"203.0.113.10","current_ipv6":"2001:db8::10","previous_ipv4":"203.0.113.10","previous_ipv6":"2001:db8::9","token_id":"dyn7f3a91c4","detail":{"status_from":"late","status_to":"online","status_changed":true},"occurred_at":"2026-08-25T13:20:27Z","received_at":"2026-08-25T13:20:28Z"}},"DynEventListResponse":{"type":"object","required":["events","next_cursor","has_more"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/DynEventResponse"}},"next_cursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next page in descending event order."},"has_more":{"type":"boolean","example":false}},"example":{"events":[],"next_cursor":null,"has_more":false}},"DynWebhookEndpointCreateRequest":{"type":"object","additionalProperties":false,"required":["name","url","event_types"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"NOC receiver"},"url":{"type":"string","format":"uri","minLength":1,"maxLength":2048,"description":"Public HTTPS webhook target. Private, local, and reserved targets are rejected.","example":"https://hooks.example.com/dynway"},"event_types":{"type":"array","minItems":1,"maxItems":9,"uniqueItems":true,"items":{"type":"string","enum":["dyn.update.good","dyn.update.nochg","dyn.update.auth_failed","dyn.update.abuse_limited","dyn.update.server_error","dyn.name.late","dyn.name.offline","dyn.name.dns_drift","dyn.name.recovered"]},"example":["dyn.update.good","dyn.name.dns_drift"]},"is_active":{"type":"boolean","default":true,"example":true}},"example":{"name":"NOC receiver","url":"https://hooks.example.com/dynway","event_types":["dyn.update.good","dyn.name.dns_drift"],"is_active":true}},"DynWebhookEndpointUpdateRequest":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"nullable":true},"url":{"type":"string","format":"uri","minLength":1,"maxLength":2048,"nullable":true},"event_types":{"type":"array","minItems":1,"maxItems":9,"uniqueItems":true,"nullable":true,"items":{"type":"string","enum":["dyn.update.good","dyn.update.nochg","dyn.update.auth_failed","dyn.update.abuse_limited","dyn.update.server_error","dyn.name.late","dyn.name.offline","dyn.name.dns_drift","dyn.name.recovered"]}},"is_active":{"type":"boolean","nullable":true}},"example":{"event_types":["dyn.name.dns_drift","dyn.name.recovered"],"is_active":true}},"DynWebhookEndpoint":{"type":"object","required":["id","name","url","event_types","is_active","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"NOC receiver"},"url":{"type":"string","format":"uri","example":"https://hooks.example.com/dynway"},"event_types":{"type":"array","items":{"type":"string"},"example":["dyn.update.good","dyn.name.offline"]},"is_active":{"type":"boolean","example":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"55555555-5555-5555-5555-555555555555","name":"NOC receiver","url":"https://hooks.example.com/dynway","event_types":["dyn.update.good","dyn.name.offline"],"is_active":true,"created_at":"2026-08-25T13:20:27Z","updated_at":"2026-08-25T13:20:27Z"}},"DynWebhookEndpointSecretResponse":{"type":"object","required":["endpoint","signing_secret","warning"],"properties":{"endpoint":{"$ref":"#/components/schemas/DynWebhookEndpoint"},"signing_secret":{"type":"string","description":"HMAC signing secret returned only on create or rotation. It is stored in Vault, never maindb."},"warning":{"type":"string","example":"Store this signing secret now. It cannot be retrieved later."}}},"DynWebhookEndpointListResponse":{"type":"object","required":["endpoints","quota","used"],"properties":{"endpoints":{"type":"array","items":{"$ref":"#/components/schemas/DynWebhookEndpoint"}},"quota":{"type":"integer","example":5},"used":{"type":"integer","example":1}},"example":{"endpoints":[],"quota":5,"used":0}},"DynWebhookDelivery":{"type":"object","required":["id","endpoint_id","source_event_id","event_type","status","attempt_count","max_attempts","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"endpoint_id":{"type":"string","format":"uuid"},"endpoint_name":{"type":"string","nullable":true,"example":"NOC receiver"},"source_event_id":{"type":"string","format":"uuid","description":"The customer-visible Dynway source event ID."},"event_type":{"type":"string","example":"dyn.name.offline"},"status":{"type":"string","enum":["pending","delivering","delivered","failed","exhausted","cancelled"],"example":"failed"},"attempt_count":{"type":"integer","minimum":0,"example":1},"max_attempts":{"type":"integer","minimum":1,"maximum":10,"example":5},"response_code":{"type":"integer","nullable":true,"minimum":100,"maximum":599,"example":422},"response_body_sample":{"type":"string","nullable":true,"maxLength":4096,"description":"Bounded sample returned by the customer endpoint."},"error_message":{"type":"string","nullable":true,"maxLength":2048,"description":"Sanitized delivery diagnostic; never contains a signing secret or Vault path."},"next_retry_at":{"type":"string","format":"date-time","nullable":true},"attempted_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"delivered_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"66666666-6666-6666-6666-666666666666","endpoint_id":"55555555-5555-5555-5555-555555555555","endpoint_name":"NOC receiver","source_event_id":"33333333-3333-3333-3333-333333333333","event_type":"dyn.name.offline","status":"failed","attempt_count":1,"max_attempts":5,"response_code":422,"response_body_sample":"invalid callback payload","error_message":"http_422","next_retry_at":null,"attempted_at":"2026-08-25T13:20:27Z","created_at":"2026-08-25T13:20:20Z","delivered_at":null}},"DynWebhookDeliveryListResponse":{"type":"object","required":["deliveries","next_cursor","has_more"],"properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/DynWebhookDelivery"}},"next_cursor":{"type":"string","nullable":true,"description":"Opaque descending delivery-history cursor."},"has_more":{"type":"boolean","example":false}},"example":{"deliveries":[],"next_cursor":null,"has_more":false}},"DynWebhookEndpointQuotaExceededError":{"type":"object","properties":{"detail":{"type":"object","required":["code","plan","used","limit"],"properties":{"code":{"type":"string","example":"dyn_webhook_endpoint_quota_exceeded"},"plan":{"type":"string","example":"business_fleet"},"used":{"type":"integer","example":5},"limit":{"type":"integer","example":5}}}}},"DynQuotaExceededError":{"type":"object","properties":{"detail":{"type":"object","required":["code","plan","used","limit"],"properties":{"code":{"type":"string","example":"dyn_name_quota_exceeded"},"plan":{"type":"string","example":"business_fleet"},"used":{"type":"integer","example":50},"limit":{"type":"integer","example":50}}}}},"DynwayUnavailableError":{"type":"object","properties":{"detail":{"type":"object","required":["code","plan","reason"],"properties":{"code":{"type":"string","example":"dynway_not_available"},"plan":{"type":"string","example":"home_free"},"reason":{"type":"string","example":"service_not_active"}}}}},"DynwayFeatureNotAvailableError":{"type":"object","properties":{"detail":{"type":"object","required":["code","feature","plan"],"properties":{"code":{"type":"string","example":"dynway_feature_not_available"},"feature":{"type":"string","example":"business_fleet_metadata"},"plan":{"type":"string","example":"home_free"}}}}},"DynTokenRequest":{"type":"object","properties":{"description":{"type":"string","nullable":true,"maxLength":255,"example":"home-router"}},"example":{"description":"home-router"}},"DynTokenResponse":{"type":"object","properties":{"dyn_name_id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"home"},"fqdn":{"type":"string","example":"home.dynway.eu"},"token_id":{"type":"string","example":"dyn7f3a91c4"},"username":{"type":"string","description":"Same value as token_id; use as DynDNS Basic Auth username.","example":"dyn7f3a91c4"},"password":{"type":"string","description":"Router password returned once at token creation or rotation.","example":"Abc123Abc123Abc123Abc123Abc123Abc123Abc1"},"created_at":{"type":"string","format":"date-time"},"warning":{"type":"string"}},"example":{"dyn_name_id":"22222222-2222-2222-2222-222222222222","name":"home","fqdn":"home.dynway.eu","token_id":"dyn7f3a91c4","username":"dyn7f3a91c4","password":"Abc123Abc123Abc123Abc123Abc123Abc123Abc1","created_at":"2026-08-25T10:00:00Z","warning":"Save this router password now. It cannot be retrieved later."}},"DynTokenListItem":{"type":"object","properties":{"token_id":{"type":"string","example":"dyn7f3a91c4"},"is_active":{"type":"boolean","example":true},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true}}},"DynTokenListResponse":{"type":"object","properties":{"tokens":{"type":"array","items":{"$ref":"#/components/schemas/DynTokenListItem"}}},"example":{"tokens":[{"token_id":"dyn7f3a91c4","is_active":true,"created_at":"2026-08-25T10:00:00Z","last_used_at":"2026-08-25T10:15:30Z","expires_at":null}]}},"DynDeleteResponse":{"type":"object","properties":{"name":{"type":"string","example":"home"},"fqdn":{"type":"string","example":"home.dynway.eu"},"deleted":{"type":"boolean","example":true},"dns_removal_pending":{"type":"boolean","example":true},"revoked_tokens":{"type":"integer","example":1}},"example":{"name":"home","fqdn":"home.dynway.eu","deleted":true,"dns_removal_pending":true,"revoked_tokens":1}},"DynTokenRevokeResponse":{"type":"object","properties":{"token_id":{"type":"string","example":"dyn7f3a91c4"},"revoked":{"type":"boolean","example":true}},"example":{"token_id":"dyn7f3a91c4","revoked":true}},"DynUpdateSourceResponse":{"type":"object","required":["source_type","display_label"],"description":"Customer-safe metadata for the active Dynway updater. It never includes a password, secret, or credential identifier.","properties":{"source_type":{"type":"string","enum":["router","desktop"],"example":"desktop"},"display_label":{"type":"string","example":"Office Mac"},"last_seen_at":{"type":"string","format":"date-time","nullable":true},"last_update_at":{"type":"string","format":"date-time","nullable":true}},"example":{"source_type":"desktop","display_label":"Office Mac","last_seen_at":"2026-08-26T10:15:30Z","last_update_at":"2026-08-26T10:15:30Z"}},"DynUpdateSourceStateResponse":{"type":"object","required":["name","fqdn"],"properties":{"name":{"type":"string","example":"home"},"fqdn":{"type":"string","example":"home.dynway.eu"},"active_source":{"allOf":[{"$ref":"#/components/schemas/DynUpdateSourceResponse"}],"nullable":true,"description":"Null when the name has no active router or desktop updater."}},"example":{"name":"home","fqdn":"home.dynway.eu","active_source":{"source_type":"desktop","display_label":"Office Mac","last_seen_at":"2026-08-26T10:15:30Z","last_update_at":"2026-08-26T10:15:30Z"}}},"DynDesktopAgentEnrollRequest":{"type":"object","additionalProperties":false,"required":["device_label","platform"],"properties":{"device_label":{"type":"string","minLength":1,"maxLength":120,"example":"Office Mac"},"platform":{"type":"string","enum":["windows","macos","linux"],"example":"macos"},"version":{"type":"string","nullable":true,"maxLength":80,"example":"1.0.0"}},"example":{"device_label":"Office Mac","platform":"macos","version":"1.0.0"}},"DynDesktopAgentReplaceRequest":{"type":"object","additionalProperties":false,"required":["device_label","platform","confirm_replace"],"properties":{"device_label":{"type":"string","minLength":1,"maxLength":120,"example":"Office Mac"},"platform":{"type":"string","enum":["windows","macos","linux"],"example":"macos"},"version":{"type":"string","nullable":true,"maxLength":80,"example":"1.0.0"},"confirm_replace":{"type":"boolean","description":"Must be true to revoke the existing active source and replace it atomically.","example":true}},"example":{"device_label":"Office Mac","platform":"macos","version":"1.0.0","confirm_replace":true}},"DynDesktopAgent":{"type":"object","required":["id","dyn_name_id","token_id","device_label","platform","is_active","created_at"],"description":"A desktop updater installation. token_id is an opaque support identifier, not a secret.","properties":{"id":{"type":"string","format":"uuid"},"dyn_name_id":{"type":"string","format":"uuid"},"token_id":{"type":"string","pattern":"^dsk[a-z0-9]{12}$","example":"dsk7f3a91c4ab56"},"device_label":{"type":"string","example":"Office Mac"},"platform":{"type":"string","enum":["windows","macos","linux"],"example":"macos"},"version":{"type":"string","nullable":true,"example":"1.0.0"},"is_active":{"type":"boolean","example":true},"created_at":{"type":"string","format":"date-time"},"last_seen_at":{"type":"string","format":"date-time","nullable":true},"last_update_at":{"type":"string","format":"date-time","nullable":true},"rotated_at":{"type":"string","format":"date-time","nullable":true},"revoked_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"55555555-5555-5555-5555-555555555555","dyn_name_id":"22222222-2222-2222-2222-222222222222","token_id":"dsk7f3a91c4ab56","device_label":"Office Mac","platform":"macos","version":"1.0.0","is_active":true,"created_at":"2026-08-26T10:00:00Z","last_seen_at":"2026-08-26T10:15:30Z","last_update_at":"2026-08-26T10:15:30Z","rotated_at":null,"revoked_at":null}},"DynDesktopAgentCredentialResponse":{"allOf":[{"$ref":"#/components/schemas/DynDesktopAgent"},{"type":"object","required":["secret","warning"],"properties":{"secret":{"type":"string","description":"One-time desktop updater secret. It is returned only on enrollment, replacement, or rotation and cannot be retrieved later."},"warning":{"type":"string","example":"Save this desktop credential now. It cannot be retrieved later."}}}]},"DynDesktopAgentListResponse":{"type":"object","required":["agents"],"properties":{"agents":{"type":"array","items":{"$ref":"#/components/schemas/DynDesktopAgent"}}},"example":{"agents":[]}},"DynDesktopAgentRevokeResponse":{"type":"object","required":["agent_id","revoked"],"properties":{"agent_id":{"type":"string","format":"uuid"},"revoked":{"type":"boolean","example":true}},"example":{"agent_id":"55555555-5555-5555-5555-555555555555","revoked":true}},"DynUpdateSourceConflictError":{"type":"object","properties":{"detail":{"type":"object","required":["code","active_source"],"properties":{"code":{"type":"string","example":"dyn_update_source_conflict"},"active_source":{"type":"object","required":["source_type","display_label"],"description":"Minimal conflict metadata only; no credential identifier, password, or secret is returned.","properties":{"source_type":{"type":"string","enum":["router","desktop"],"example":"router"},"display_label":{"type":"string","example":"Home router"},"last_seen_at":{"type":"string","format":"date-time","nullable":true}}}}}},"example":{"detail":{"code":"dyn_update_source_conflict","active_source":{"source_type":"router","display_label":"Home router","last_seen_at":"2026-08-26T10:15:30Z"}}}},"ReasoningMetadata":{"properties":{"summary":{"type":"string","title":"Summary","description":"Brief summary of the reasoning process"},"depth":{"anyOf":[{"type":"string","enum":["shallow","standard","deep","critical"]},{"type":"null"}],"title":"Depth","description":"Reasoning depth level","default":"standard"},"steps":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Steps","description":"Individual reasoning steps taken"},"tokens":{"type":"integer","title":"Tokens","description":"Number of tokens used for reasoning"},"raw_thinking":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Thinking","description":"Raw thinking content from the model (if available)"}},"type":"object","required":["summary","tokens"],"title":"ReasoningMetadata","description":"Metadata from reasoning-capable models (deepseek-r1, qwen3-*-thinking).\n\nWhen using reasoning models, the AI may perform chain-of-thought reasoning\nbefore generating its final response. This metadata captures that process."},"ReasoningContainer":{"type":"object","description":"Container for reasoning metadata returned by reasoning-capable models","properties":{"reasoning":{"$ref":"#/components/schemas/ReasoningMetadata"}},"example":{"reasoning":{"summary":"Model evaluated alternative interpretations and selected the most likely answer.","depth":"standard","steps":["Parsed user intent","Evaluated context","Performed logical deduction","Selected final response"],"tokens":214}}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError","example":{"loc":["body","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError","example":{"detail":[{"loc":["body","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}},"VPSInstance":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"hostname":{"type":"string"},"display_name":{"type":"string"},"status":{"type":"string","enum":["running","stopped","starting","stopping","provisioning"]},"plan_code":{"type":"string"},"region":{"type":"string"},"os_template":{"type":"string"},"ipv4_address":{"type":"string"},"ipv6_address":{"type":"string"},"vcpus":{"type":"integer"},"memory_gb":{"type":"integer"},"disk_gb":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","hostname":"web01.example.com","display_name":"Production Web Server","status":"running","plan_code":"vps-4gb-2cpu","region":"oslo","os_template":"ubuntu-22.04","ipv4_address":"185.35.202.45","ipv6_address":"2a01:4f9:c010:1234::1","vcpus":2,"memory_gb":4,"disk_gb":80,"created_at":"2024-02-15T14:30:00Z"}},"VPSList":{"type":"object","properties":{"instances":{"type":"array","items":{"$ref":"#/components/schemas/VPSInstance"}},"total":{"type":"integer"}},"example":{"instances":[{"id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","hostname":"web01.example.com","display_name":"Production Web Server","status":"running","ipv4_address":"185.35.202.45"},{"id":"c6d7e8f9-a0b1-2345-6789-0abcdef12345","hostname":"db01.example.com","display_name":"Database Server","status":"running","ipv4_address":"185.35.202.46"}],"total":2}},"VPSStatus":{"type":"object","properties":{"status":{"type":"string"},"uptime_seconds":{"type":"integer"},"cpu_usage_percent":{"type":"number"},"memory_usage_percent":{"type":"number"},"disk_usage_percent":{"type":"number"},"network_in_bytes":{"type":"integer"},"network_out_bytes":{"type":"integer"}},"example":{"status":"running","uptime_seconds":2592000,"cpu_usage_percent":12.5,"memory_usage_percent":68.3,"disk_usage_percent":42.1,"network_in_bytes":157286400,"network_out_bytes":52428800}},"VPSAction":{"type":"object","properties":{"success":{"type":"boolean"},"action":{"type":"string"},"message":{"type":"string"}},"example":{"success":true,"action":"start","message":"VPS is starting"}},"VPSCreated":{"type":"object","description":"Response when a new VPS is created","properties":{"id":{"type":"string","format":"uuid","description":"VPS instance ID"},"hostname":{"type":"string"},"status":{"type":"string","enum":["provisioning","running"]},"ipv4":{"type":"string","description":"Primary IPv4 address"},"ipv6":{"type":"string","description":"Primary IPv6 address"}},"example":{"id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","hostname":"web01.example.com","status":"provisioning","ipv4":"185.35.202.45","ipv6":"2a01:4f9:c010:1234::1"}},"VPSSnapshot":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"uuid"},"vps_id":{"type":"string","format":"uuid"},"snapshot_name":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["available","creating","deleting","error"]},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"example":{"snapshot_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","vps_id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","snapshot_name":"before_upgrade","description":"Pre-upgrade state","status":"available","created_at":"2026-04-03T03:00:00Z","completed_at":"2026-04-03T03:00:02Z"}},"VPSSnapshotCreate":{"type":"object","required":["snapshot_name"],"properties":{"snapshot_name":{"type":"string","minLength":3,"maxLength":100,"description":"Snapshot name (alphanumeric + underscore)"},"description":{"type":"string","maxLength":500,"description":"Optional description"}},"example":{"snapshot_name":"before-upgrade","description":"Snapshot before OS upgrade"}},"VPSBackup":{"type":"object","properties":{"backup_id":{"type":"string","format":"uuid"},"vps_id":{"type":"string","format":"uuid"},"backup_type":{"type":"string","enum":["automatic","manual"]},"size_gb":{"type":"number","nullable":true},"status":{"type":"string","enum":["creating","uploading","available","restoring","deleting","error"]},"started_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true}},"example":{"backup_id":"c1d2e3f4-a5b6-7890-cdef-123456789abc","vps_id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","backup_type":"automatic","size_gb":3.56,"status":"available","started_at":"2026-04-03T03:00:00Z","completed_at":"2026-04-03T03:02:30Z","expires_at":"2026-04-10T03:00:00Z"}},"VPSBackupPolicy":{"type":"object","properties":{"policy_id":{"type":"string","format":"uuid"},"vps_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"},"frequency":{"type":"string","enum":["daily","weekly"]},"time_of_day":{"type":"string","description":"HH:MM in UTC"},"day_of_week":{"type":"integer","minimum":0,"maximum":6,"nullable":true,"description":"0=Monday, only for weekly"},"retention_days":{"type":"integer","minimum":1,"maximum":90},"next_backup_at":{"type":"string","format":"date-time","nullable":true},"last_backup_at":{"type":"string","format":"date-time","nullable":true}},"example":{"policy_id":"d1e2f3a4-b5c6-7890-defa-bcdef1234567","vps_id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","enabled":true,"frequency":"daily","time_of_day":"03:00","retention_days":7,"next_backup_at":"2026-04-04T03:00:00Z"}},"VPSBackupPolicySet":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"frequency":{"type":"string","enum":["daily","weekly"],"default":"daily"},"time_of_day":{"type":"string","default":"03:00","description":"HH:MM in UTC"},"day_of_week":{"type":"integer","minimum":0,"maximum":6,"nullable":true,"description":"0=Mon, required for weekly"},"retention_days":{"type":"integer","minimum":1,"maximum":90,"default":7}},"example":{"enabled":true,"frequency":"daily","time_of_day":"03:00","retention_days":7}},"AppPlan":{"type":"object","properties":{"id":{"type":"string","description":"Plan identifier"},"name":{"type":"string","description":"Plan display name"},"description":{"type":"string","nullable":true},"cpu_cores":{"type":"number","description":"CPU cores allocated per instance"},"memory_mb":{"type":"integer","description":"Memory in MB per instance"},"disk_gb":{"type":"integer","description":"Disk space in GB"},"max_instances":{"type":"integer","description":"Maximum concurrent instances"},"max_apps_per_customer":{"type":"integer"},"max_custom_domains":{"type":"integer"},"bandwidth_gb_included":{"type":"integer","description":"Monthly bandwidth included"},"price_monthly":{"type":"number","description":"Monthly subscription price"},"hourly_rate":{"type":"number","nullable":true,"description":"Per instance-hour usage rate"},"currency":{"type":"string"},"features":{"type":"object","additionalProperties":true}},"example":{"id":"app-starter","name":"Starter","description":"For small production workloads","cpu_cores":1.0,"memory_mb":1024,"disk_gb":10,"max_instances":2,"max_apps_per_customer":5,"max_custom_domains":3,"bandwidth_gb_included":100,"price_monthly":99.0,"hourly_rate":0.15,"currency":"NOK","features":{}}},"AppRegion":{"type":"object","properties":{"code":{"type":"string","description":"Region code"},"name":{"type":"string","description":"Region display name"},"country_code":{"type":"string","description":"ISO 3166-1 alpha-2"},"app_platform_available":{"type":"boolean"}},"example":{"code":"no","name":"Norge","country_code":"NO","app_platform_available":true}},"AppSummary":{"type":"object","description":"App summary for list views","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"short_id":{"type":"string"},"region":{"type":"string"},"plan_id":{"type":"string"},"plan_name":{"type":"string"},"status":{"type":"string","enum":["creating","building","deploying","running","stopped","stopping","error","failed","deleting"]},"default_url":{"type":"string","nullable":true},"instance_count":{"type":"integer"},"running_instances":{"type":"integer"},"max_instances":{"type":"integer"},"domain_count":{"type":"integer"},"scale_to_zero_enabled":{"type":"boolean"},"is_scaled_to_zero":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"last_deployed_at":{"type":"string","format":"date-time","nullable":true},"plan_max_apps":{"type":"integer"},"plan_max_domains":{"type":"integer"}},"example":{"id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","name":"invoice-api-prod","slug":"invoice-api-prod","short_id":"q7b6t1y0","region":"no","plan_id":"app-starter","plan_name":"Starter","status":"running","default_url":"invoice-api-prod-q7b6t1y0.apps.wayscloud.services","instance_count":1,"running_instances":1,"max_instances":2,"domain_count":0,"scale_to_zero_enabled":false,"is_scaled_to_zero":false,"created_at":"2026-03-15T09:30:00Z","last_deployed_at":"2026-04-10T14:22:00Z","plan_max_apps":5,"plan_max_domains":3}},"AppList":{"type":"object","description":"Paginated list of apps","properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/AppSummary"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"}},"example":{"apps":[{"id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","name":"invoice-api-prod","slug":"invoice-api-prod","short_id":"q7b6t1y0","region":"no","plan_id":"app-starter","plan_name":"Starter","status":"running","default_url":"invoice-api-prod-q7b6t1y0.apps.wayscloud.services","instance_count":1,"running_instances":1,"max_instances":2,"domain_count":0,"scale_to_zero_enabled":false,"is_scaled_to_zero":false,"created_at":"2026-03-15T09:30:00Z","last_deployed_at":"2026-04-10T14:22:00Z","plan_max_apps":5,"plan_max_domains":3}],"total":1,"page":1,"page_size":20}},"App":{"type":"object","description":"Full app details","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"short_id":{"type":"string"},"region":{"type":"string"},"plan_id":{"type":"string"},"plan_name":{"type":"string"},"status":{"type":"string","enum":["creating","building","deploying","running","stopped","stopping","error","failed","deleting"]},"status_message":{"type":"string","nullable":true},"default_url":{"type":"string","nullable":true},"port":{"type":"integer"},"health_check_path":{"type":"string"},"min_instances":{"type":"integer"},"max_instances":{"type":"integer"},"scale_to_zero_enabled":{"type":"boolean"},"idle_timeout_minutes":{"type":"integer"},"is_scaled_to_zero":{"type":"boolean"},"active_revision_id":{"type":"string","nullable":true},"active_deployment_id":{"type":"string","nullable":true},"last_deployed_at":{"type":"string","format":"date-time","nullable":true},"active_image":{"type":"string","nullable":true,"description":"Currently deployed container image URI"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time","nullable":true},"volume":{"type":"object","nullable":true,"description":"Persistent volume metadata. Null if no volume attached."}},"example":{"id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","name":"invoice-api-prod","slug":"invoice-api-prod","short_id":"q7b6t1y0","region":"no","plan_id":"app-starter","plan_name":"Starter","status":"running","status_message":null,"default_url":"invoice-api-prod-q7b6t1y0.apps.wayscloud.services","port":3000,"health_check_path":"/health","min_instances":1,"max_instances":2,"scale_to_zero_enabled":false,"idle_timeout_minutes":15,"is_scaled_to_zero":false,"active_revision_id":"rev_01jxr5n4p8m2w7k3q9b6t0y1z","active_deployment_id":"dep_01jxr5n4p8m2w7k3q9b6t0y1z","last_deployed_at":"2026-04-10T14:22:00Z","active_image":"ghcr.io/acme-corp/invoice-api:v2.4.1","created_at":"2026-03-15T09:30:00Z","updated_at":"2026-04-10T14:22:00Z","volume":null}},"AppDeployResponse":{"type":"object","description":"Deployment initiated response","properties":{"app_id":{"type":"string"},"revision_id":{"type":"string"},"deployment_id":{"type":"string"},"status":{"type":"string","description":"Initial deployment status (pending)"},"message":{"type":"string"},"preflight_warnings":{"type":"array","items":{"type":"object"},"description":"Warnings from pre-deploy checks"}},"example":{"app_id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","revision_id":"rev_01jxr6a2b3c4d5e6f7g8h9i0j","deployment_id":"dep_01jxr6a2b3c4d5e6f7g8h9i0j","status":"pending","message":"Deployment started for image ghcr.io/acme-corp/invoice-api:v2.4.1","preflight_warnings":[]}},"AppAction":{"type":"object","description":"Result of an app lifecycle action","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"App started"}},"AppLogs":{"type":"object","description":"Container log output. This is live stdout/stderr from the running container, not a historical log archive. Logs may be empty for stopped or idle apps.","properties":{"app_id":{"type":"string"},"instance_id":{"type":"string","nullable":true},"logs":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"message":{"type":"string"},"stream":{"type":"string","enum":["stdout","stderr"]}}},"description":"Log entries from container output"},"has_more":{"type":"boolean","description":"True if more log lines are available beyond the requested limit"}},"example":{"app_id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","instance_id":null,"logs":[{"timestamp":"2026-04-12T14:22:01Z","message":"Server listening on port 3000","stream":"stdout"},{"timestamp":"2026-04-12T14:22:03Z","message":"Connected to database","stream":"stdout"}],"has_more":false}},"StorageBucket":{"type":"object","properties":{"name":{"type":"string"},"region":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"size_bytes":{"type":"integer"},"object_count":{"type":"integer"},"is_public":{"type":"boolean"}},"example":{"name":"company-assets","region":"oslo","created_at":"2024-01-20T09:00:00Z","size_bytes":53687091200,"object_count":12458,"is_public":false}},"StorageBucketList":{"type":"object","properties":{"buckets":{"type":"array","items":{"$ref":"#/components/schemas/StorageBucket"}},"total":{"type":"integer"}},"example":{"buckets":[{"name":"company-assets","region":"oslo","size_bytes":53687091200,"object_count":12458,"is_public":false},{"name":"app-backups","region":"oslo","size_bytes":107374182400,"object_count":89,"is_public":false}],"total":2}},"S3Credentials":{"type":"object","properties":{"access_key_id":{"type":"string"},"secret_access_key":{"type":"string"},"endpoint_url":{"type":"string"},"region":{"type":"string"}},"example":{"access_key_id":"AKWC3XJQK7HMNTUVWXYZ","secret_access_key":"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY","endpoint_url":"https://storage.wayscloud.services","region":"oslo"}},"StorageBucketCreated":{"type":"object","description":"Response when a new bucket is created","properties":{"success":{"type":"boolean"},"bucket_name":{"type":"string"},"message":{"type":"string"},"endpoint":{"type":"string"},"region":{"type":"string"}},"example":{"success":true,"bucket_name":"my-app-uploads","message":"Bucket 'my-app-uploads' created successfully","endpoint":"https://storage.wayscloud.services","region":"oslo"}},"DNSZone":{"type":"object","properties":{"name":{"type":"string"},"status":{"type":"string","enum":["active","pending","disabled"]},"dnssec_enabled":{"type":"boolean"},"record_count":{"type":"integer"},"nameservers":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"}},"example":{"name":"example.com","status":"active","dnssec_enabled":true,"record_count":24,"nameservers":["grieg.wayscloud.no","bohr.wayscloud.dk","lindgren.wayscloud.se","aalto.wayscloud.fi"],"created_at":"2024-01-10T08:00:00Z"}},"DNSZoneList":{"type":"object","properties":{"zones":{"type":"array","items":{"$ref":"#/components/schemas/DNSZone"}},"total":{"type":"integer"}},"example":{"zones":[{"name":"example.com","status":"active","dnssec_enabled":true,"record_count":24},{"name":"myapp.io","status":"active","dnssec_enabled":false,"record_count":8}],"total":2}},"DNSRecord":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["A","AAAA","CNAME","MX","TXT","NS","SRV","CAA"]},"content":{"type":"string"},"ttl":{"type":"integer"},"priority":{"type":"integer"}},"example":{"id":"e8f9a0b1-c2d3-4567-890a-bcdef1234567","name":"www","type":"A","content":"185.35.202.45","ttl":3600,"priority":null}},"DNSRecordList":{"type":"object","properties":{"records":{"type":"array","items":{"$ref":"#/components/schemas/DNSRecord"}},"total":{"type":"integer"}},"example":{"records":[{"id":"e8f9a0b1-c2d3-4567-890a-bcdef1234567","name":"@","type":"A","content":"185.35.202.45","ttl":3600},{"id":"f9a0b1c2-d3e4-5678-90ab-cdef12345678","name":"www","type":"CNAME","content":"example.com","ttl":3600},{"id":"a0b1c2d3-e4f5-6789-0abc-def123456789","name":"@","type":"MX","content":"mail.example.com","ttl":3600,"priority":10}],"total":3}},"DNSStatistics":{"type":"object","properties":{"queries_today":{"type":"integer"},"queries_this_month":{"type":"integer"},"top_record_types":{"type":"object"},"response_time_ms_avg":{"type":"number"}},"example":{"queries_today":15420,"queries_this_month":425000,"top_record_types":{"A":8500,"AAAA":4200,"MX":1800,"TXT":920},"response_time_ms_avg":12.5}},"DNSSECInfo":{"type":"object","properties":{"enabled":{"type":"boolean"},"ds_records":{"type":"array","items":{"type":"string"}},"algorithm":{"type":"string"}},"example":{"enabled":true,"ds_records":["example.com. 3600 IN DS 12345 13 2 49FD46E6C4B45C55D4AC49FD46E6C4B45C55D4AC..."],"algorithm":"ECDSAP256SHA256"}},"Region":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"country":{"type":"string"},"available_services":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","maintenance","deprecated"]}},"example":{"code":"oslo","name":"Oslo, Norway","country":"NO","available_services":["database","storage","vps","dns","llm"],"status":"active"}},"RegionList":{"type":"object","properties":{"regions":{"type":"array","items":{"$ref":"#/components/schemas/Region"}},"total":{"type":"integer"}},"example":{"regions":[{"code":"oslo","name":"Oslo, Norway","country":"NO","status":"active"},{"code":"stockholm","name":"Stockholm, Sweden","country":"SE","status":"active"}],"total":2}},"RegionPricing":{"type":"object","properties":{"region":{"type":"string"},"currency":{"type":"string"},"services":{"type":"object"}},"example":{"region":"oslo","currency":"NOK","services":{"vps":{"vps-2gb-1cpu":99,"vps-4gb-2cpu":199,"vps-8gb-4cpu":399},"storage":{"per_gb_month":0.5},"database":{"postgresql_per_gb":2.5,"mariadb_per_gb":2.0}}}},"DatabaseInstance":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"friendly_name":{"type":"string","description":"User-chosen display name"},"technical_name":{"type":"string","description":"System-generated name"},"database_type":{"type":"string","enum":["postgresql","mariadb"]},"version":{"type":"string"},"status":{"type":"string","enum":["provisioning","active","maintenance","suspended","deleted"]},"region":{"type":"string"},"storage_gb":{"type":"number"},"created_at":{"type":"string","format":"date-time"},"connection":{"$ref":"#/components/schemas/DatabaseCredentials"}},"example":{"id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","friendly_name":"webapp_prod_db","technical_name":"cust_6dd1d906_pg_20251120150438_35bf9f71","database_type":"postgresql","version":"16","status":"active","region":"oslo","storage_gb":2.4,"created_at":"2025-11-20T15:04:38Z","connection":{"host":"pg.dbaas.wayscloud.services","port":5432,"database":"cust_6dd1d906_pg_20251120150438_35bf9f71","username":"app_user","connection_string":"postgresql://app_user:***@pg.dbaas.wayscloud.services:5432/cust_6dd1d906_pg_20251120150438_35bf9f71"}}},"DatabaseList":{"type":"object","properties":{"databases":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseInstance"}},"total":{"type":"integer"}},"example":{"databases":[{"id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","friendly_name":"webapp_prod_db","database_type":"postgresql","version":"16","status":"active","storage_gb":2.4},{"id":"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d","friendly_name":"analytics_db","database_type":"mariadb","version":"11.4","status":"active","storage_gb":0.8}],"total":2}},"DatabaseEngine":{"type":"object","properties":{"name":{"type":"string"},"versions":{"type":"array","items":{"type":"string"}},"default_version":{"type":"string"}},"example":{"name":"postgresql","versions":["14","15","16"],"default_version":"16"}},"DatabaseEngineList":{"type":"object","properties":{"engines":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseEngine"}}},"example":{"engines":[{"name":"postgresql","versions":["14","15","16"],"default_version":"16"},{"name":"mariadb","versions":["10.11","11.4"],"default_version":"11.4"}]}},"DatabaseCredentials":{"type":"object","properties":{"host":{"type":"string","description":"Database server hostname"},"port":{"type":"integer","description":"Database port"},"database":{"type":"string","description":"Database name"},"username":{"type":"string","description":"Database username"},"password":{"type":"string","description":"Database password (only shown once at creation)"},"connection_string":{"type":"string","description":"Complete connection string"}},"example":{"host":"pg.dbaas.wayscloud.services","port":5432,"database":"cust_6dd1d906_pg_20251120150438_35bf9f71","username":"app_user","password":"Xk9mN2pQ4rS7tV3w","connection_string":"postgresql://app_user:Xk9mN2pQ4rS7tV3w@pg.dbaas.wayscloud.services:5432/cust_6dd1d906_pg_20251120150438_35bf9f71"}},"DatabaseMetrics":{"type":"object","properties":{"database_id":{"type":"string","format":"uuid"},"storage_gb":{"type":"number","description":"Storage used in GB"},"backup_gb":{"type":"number","description":"Backup storage in GB"},"active_connections":{"type":"integer","description":"Current active connections"},"max_connections":{"type":"integer","description":"Maximum connections allowed"},"total_queries":{"type":"integer","description":"Total queries executed"},"queries_per_sec":{"type":"number","description":"Queries per second"},"cache_hit_ratio":{"type":"number","description":"Cache hit ratio percentage"},"cpu_percent":{"type":"number","description":"CPU usage percentage"},"memory_percent":{"type":"number","description":"Memory usage percentage"},"measured_at":{"type":"string","format":"date-time"}},"example":{"database_id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","storage_gb":2.4,"backup_gb":1.2,"active_connections":8,"max_connections":100,"total_queries":1247832,"queries_per_sec":42.7,"cache_hit_ratio":98.4,"cpu_percent":12.3,"memory_percent":34.8,"measured_at":"2025-11-27T14:30:00Z"}},"DatabaseSnapshot":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"database_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"size_gb":{"type":"number"},"status":{"type":"string","enum":["creating","available","restoring","failed"]},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d","database_id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","name":"before_migration_v2","size_gb":2.1,"status":"available","created_at":"2025-11-20T10:00:00Z"}},"DatabaseSnapshotList":{"type":"object","properties":{"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseSnapshot"}},"total":{"type":"integer"}},"example":{"snapshots":[{"id":"9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d","name":"before_migration_v2","size_gb":2.1,"status":"available","created_at":"2025-11-20T10:00:00Z"},{"id":"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e","name":"weekly_backup_47","size_gb":1.9,"status":"available","created_at":"2025-11-17T03:00:00Z"}],"total":2}},"DatabaseHistory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"action":{"type":"string","enum":["created","deleted","restored","modified","snapshot_created"]},"database_name":{"type":"string"},"database_type":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"details":{"type":"object"}},"example":{"id":"c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f","action":"created","database_name":"webapp_prod_db","database_type":"postgresql","timestamp":"2025-11-20T15:04:38Z","details":{"version":"16","region":"oslo"}}},"DatabaseQuota":{"type":"object","properties":{"used":{"type":"integer","description":"Number of databases created"},"limit":{"type":"integer","description":"Maximum databases allowed"},"storage_used_gb":{"type":"number"},"storage_limit_gb":{"type":"number"}},"example":{"used":2,"limit":10,"storage_used_gb":3.2,"storage_limit_gb":50.0}},"OperationSuccess":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"Operation completed successfully"}},"OpenAIModel":{"type":"object","properties":{"id":{"type":"string","description":"Model identifier","example":"mixtral-8x7b"},"object":{"type":"string","enum":["model"],"default":"model"},"owned_by":{"type":"string","example":"wayscloud"},"created":{"type":"integer","description":"Unix timestamp"}},"example":{"id":"mixtral-8x7b","object":"model","owned_by":"wayscloud","created":1700000000}},"OpenAIModelList":{"type":"object","properties":{"object":{"type":"string","enum":["list"],"default":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/OpenAIModel"}}},"example":{"object":"list","data":[{"id":"mixtral-8x7b","object":"model","owned_by":"wayscloud","created":1700000000},{"id":"deepseek-v3","object":"model","owned_by":"wayscloud","created":1700000000}]}},"DomainVerification":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string","description":"Domain name (normalized to lowercase/punycode)"},"purpose":{"type":"string","enum":["email","custom_host","webhook","link_branding","verify_channel","other"]},"status":{"type":"string","enum":["pending","verified","failed","revoked"]},"verification_method":{"type":"string","enum":["DNS_TXT","DNS_CNAME"]},"verification_token":{"type":"string","description":"Token to add to DNS record"},"dns_instructions":{"type":"object","description":"Instructions for DNS setup"},"verification_attempts":{"type":"integer"},"verified_at":{"type":"string","format":"date-time"},"last_checked_at":{"type":"string","format":"date-time"},"failed_reason":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","purpose":"email","status":"pending","verification_method":"DNS_TXT","verification_token":"wayscloud-domain-verification=abc123xyz789","dns_instructions":{"record_type":"TXT","host":"_wayscloud.example.com","value":"wayscloud-domain-verification=abc123xyz789"},"verification_attempts":0,"verified_at":null,"last_checked_at":null,"failed_reason":null,"created_at":"2025-12-08T10:00:00Z"}},"DomainVerificationSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"purpose":{"type":"string"},"status":{"type":"string"},"verification_method":{"type":"string"},"verified_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","purpose":"email","status":"verified","verification_method":"DNS_TXT","verified_at":"2025-12-08T10:30:00Z","created_at":"2025-12-08T10:00:00Z"}},"DomainVerificationList":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/DomainVerificationSummary"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"}},"example":{"domains":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","purpose":"email","status":"verified"},{"id":"b2c3d4e5-f6a7-8901-bcde-f23456789012","domain":"app.example.com","purpose":"custom_host","status":"pending"}],"total":2,"page":1,"page_size":20}},"RegisterDomainRequest":{"type":"object","required":["domain","purpose"],"properties":{"domain":{"type":"string","description":"Domain to verify (e.g., example.com)"},"purpose":{"type":"string","enum":["email","custom_host","webhook","link_branding","verify_channel","other"]},"verification_method":{"type":"string","enum":["DNS_TXT","DNS_CNAME"],"default":"DNS_TXT"},"metadata":{"type":"object","description":"Optional metadata"}},"example":{"domain":"example.com","purpose":"email","verification_method":"DNS_TXT"}},"VerifyDomainResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"status":{"type":"string"},"verified":{"type":"boolean"},"message":{"type":"string"},"dns_records_found":{"type":"array","items":{"type":"string"}},"checked_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","status":"verified","verified":true,"message":"Domain verified successfully","dns_records_found":["wayscloud-domain-verification=abc123xyz789"],"checked_at":"2025-12-08T10:30:00Z"}},"DomainVerificationHistory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"event_type":{"type":"string"},"previous_status":{"type":"string"},"new_status":{"type":"string"},"dns_response":{"type":"object"},"error_message":{"type":"string"},"performed_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"c3d4e5f6-a7b8-9012-cdef-345678901234","event_type":"check_completed","previous_status":"pending","new_status":"verified","dns_response":{"records_found":true,"actual_values":["wayscloud-domain-verification=abc123xyz789"]},"performed_by":"api","created_at":"2025-12-08T10:30:00Z"}},"BackupPolicyRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Enable/disable automated backups"},"frequency":{"type":"string","enum":["hourly","daily","weekly"],"title":"Frequency","description":"Backup frequency","default":"daily"},"retention_days":{"type":"integer","title":"Retention Days","description":"How many days to keep backups","default":7},"time_of_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Of Day","description":"Time for daily backups (HH:MM format)"}},"type":"object","required":["enabled"],"title":"BackupPolicyRequest","description":"Configure automatic backups for your database.\n\nSet up scheduled backups to protect your data. Choose how often to back up\nand how long to keep backups. Daily backups at 02:00 with 7-day retention\nis recommended for most use cases.","example":{"enabled":true,"frequency":"daily","retention_days":7,"time_of_day":"02:00"}},"BackupPolicyResponse":{"properties":{"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"enabled":{"type":"boolean","title":"Enabled"},"frequency":{"type":"string","title":"Frequency"},"retention_days":{"type":"integer","title":"Retention Days"},"time_of_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Of Day"},"last_backup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Backup"},"next_backup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Backup"}},"type":"object","required":["db_name","db_type","enabled","frequency","retention_days"],"title":"BackupPolicyResponse","description":"Your current backup policy settings.\n\nShows when the last backup ran and when the next one is scheduled.\nBackups older than `retention_days` are automatically deleted.","example":{"db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","enabled":true,"frequency":"daily","retention_days":7,"time_of_day":"02:00","last_backup":"2025-11-26T02:00:15Z","next_backup":"2025-11-27T02:00:00Z"}},"DatabaseCreateRequest":{"properties":{"db_type":{"type":"string","enum":["postgresql","mariadb"],"title":"Db Type","description":"Database type (postgresql or mariadb)"},"requested_name":{"anyOf":[{"type":"string","maxLength":30,"pattern":"^[a-zA-Z0-9_]+$"},{"type":"null"}],"title":"Requested Name","description":"Optional custom database name suffix (alphanumeric and underscore only, max 30 chars). Creates deterministic name: cust_{customer_id}_{requested_name}"}},"type":"object","required":["db_type"],"title":"DatabaseCreateRequest","description":"Create a new managed database.\n\nSimply specify the database type (PostgreSQL or MariaDB) and we'll provision\na fully managed database with auto-generated credentials. You'll receive the\nconnection details in the response.\n\n**Terraform users**: Use `requested_name` for deterministic, reproducible database names\nthat won't change between deployments.","example":{"db_type":"postgresql","requested_name":"webapp_prod_db"}},"DatabaseCreateResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"db_type":{"type":"string","title":"Db Type"},"db_name":{"type":"string","title":"Db Name"},"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"host":{"type":"string","title":"Host"},"port":{"type":"integer","title":"Port"},"connection_string":{"type":"string","title":"Connection String"},"message":{"type":"string","title":"Message"},"password_generated":{"type":"boolean","title":"Password Generated","description":"True if password was auto-generated","default":false},"idempotent":{"type":"boolean","title":"Idempotent","description":"True if database already existed (no new creation)","default":false}},"type":"object","required":["success","db_type","db_name","username","password","host","port","connection_string","message"],"title":"DatabaseCreateResponse","description":"Your new database connection details.\n\n**Important**: Save the `password` securely - it cannot be retrieved later.\nUse the `connection_string` to connect directly from your application.\n\nIf `idempotent` is true, the database already existed and you're receiving\nthe existing credentials (useful for Terraform re-runs).","example":{"success":true,"db_type":"postgresql","db_name":"cust_6dd1d906_pg_webapp_prod_db","username":"cust_6dd1d906_pg_webapp_prod_db","password":"Xk9mN2pQ4rS7tV3wYz","host":"db.no.wayscloud.services","port":5432,"connection_string":"postgresql://cust_6dd1d906_pg_webapp_prod_db:Xk9mN2pQ4rS7tV3wYz@db.no.wayscloud.services:5432/cust_6dd1d906_pg_webapp_prod_db","message":"Database created successfully","password_generated":true,"idempotent":false}},"DatabaseCredentialsResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"db_name":{"type":"string","title":"Db Name"},"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"host":{"type":"string","title":"Host"},"port":{"type":"integer","title":"Port"},"connection_string":{"type":"string","title":"Connection String"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["success","db_name","username","password","host","port","connection_string"],"title":"DatabaseCredentialsResponse","description":"Your database credentials retrieved from secure storage.\n\nUse this if you've lost your connection details. The password is securely\nstored and can be retrieved at any time.","example":{"success":true,"db_name":"cust_6dd1d906_pg_webapp_prod_db","username":"cust_6dd1d906_pg_webapp_prod_db","password":"Xk9mN2pQ4rS7tV3wYz","host":"db.no.wayscloud.services","port":5432,"connection_string":"postgresql://cust_6dd1d906_pg_webapp_prod_db:Xk9mN2pQ4rS7tV3wYz@db.no.wayscloud.services:5432/cust_6dd1d906_pg_webapp_prod_db","created_at":"2025-10-15T14:30:00Z"}},"DatabaseDeleteRequest":{"properties":{"db_type":{"type":"string","enum":["postgresql","mariadb"],"title":"Db Type"},"db_name":{"type":"string","title":"Db Name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"}},"type":"object","required":["db_type","db_name"],"title":"DatabaseDeleteRequest","description":"Delete a database permanently.\n\n**Warning**: This action is irreversible. All data will be permanently deleted.\nMake sure you have backups before proceeding.","example":{"db_type":"postgresql","db_name":"cust_6dd1d906_pg_webapp_prod_db","username":"cust_6dd1d906_pg_webapp_prod_db"}},"DatabaseDeleteResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","message"],"title":"DatabaseDeleteResponse","description":"Confirmation that your database was deleted.\n\nIf successful, the database and all its data have been permanently removed.","example":{"success":true,"message":"Database 'cust_6dd1d906_pg_webapp_prod_db' deleted successfully"}},"DatabaseInfoResponse":{"properties":{"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"size_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Size Mb"},"connection_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Connection Count"},"connection_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Connection Limit"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"}},"type":"object","required":["db_name","db_type"],"title":"DatabaseInfoResponse","description":"Detailed information about your database.\n\nShows current storage usage, active connections, and other metrics\nto help you monitor and plan capacity.","example":{"db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"connection_count":12,"connection_limit":100,"created_at":"2025-10-15T14:30:00Z","owner":"cust_6dd1d906_pg_webapp_prod_db"}},"DatabaseListResponse":{"properties":{"db_type":{"type":"string","title":"Db Type"},"databases":{"items":{"type":"string"},"type":"array","title":"Databases"}},"type":"object","required":["db_type","databases"],"title":"DatabaseListResponse","description":"Your databases of a specific type.\n\nReturns the database names you own. To get detailed information about a \nspecific database (size, connections, etc.), use the `/info` endpoint.","example":{"db_type":"postgresql","databases":["cust_6dd1d906_pg_webapp_prod_db","cust_6dd1d906_pg_analytics_db","cust_6dd1d906_pg_staging_db"]}},"DatabaseScaleRequest":{"properties":{"connection_limit":{"anyOf":[{"type":"integer","maximum":1000.0,"minimum":10.0},{"type":"null"}],"title":"Connection Limit","description":"Maximum connections allowed (10-1000)"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description","description":"Optional description/notes for this database"}},"type":"object","title":"DatabaseScaleRequest","description":"Request to scale database resources","example":{"connection_limit":200,"description":"Increased connection limit for production traffic"}},"FirewallAddRequest":{"properties":{"ip_address":{"type":"string","title":"Ip Address","description":"IP address or CIDR block to whitelist"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description","description":"Optional rule description (e.g., 'Office network')"}},"type":"object","required":["ip_address"],"title":"FirewallAddRequest","description":"Add an IP address to your database's firewall whitelist.\n\nSimpler than FirewallAllowRequest - the database is specified in the URL path,\nso you only need to provide the IP address.","example":{"ip_address":"192.0.2.100/32","description":"Office VPN connection"}},"FirewallListResponse":{"properties":{"rules":{"items":{"$ref":"#/components/schemas/FirewallRule"},"type":"array","title":"Rules"},"total":{"type":"integer","title":"Total","description":"Total number of rules"}},"type":"object","required":["rules","total"],"title":"FirewallListResponse","description":"All firewall rules for your databases.\n\nShows which IP addresses are currently allowed to connect to each of your databases.","example":{"rules":[{"rule_id":"fw-a1b2c3d4-5678-90ab-cdef-123456789abc","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"192.0.2.100/32","port":5432,"description":"Office VPN connection","created_at":"2025-10-20T09:15:00Z"},{"rule_id":"fw-b2c3d4e5-6789-01bc-def0-234567890bcd","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"203.0.113.50/32","port":5432,"description":"Home office","created_at":"2025-10-22T14:30:00Z"}],"total":2}},"FirewallRule":{"properties":{"rule_id":{"type":"string","title":"Rule Id","description":"Unique rule identifier (UUID)"},"db_name":{"type":"string","title":"Db Name","description":"Database name"},"db_type":{"type":"string","title":"Db Type","description":"Database type"},"ip_address":{"type":"string","title":"Ip Address","description":"IP address or CIDR block"},"port":{"type":"integer","title":"Port","description":"Database port"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional rule description"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["rule_id","db_name","db_type","ip_address","port","created_at"],"title":"FirewallRule","description":"A firewall rule that allows access to your database.\n\nEach rule whitelists a specific IP address or CIDR block to connect to your database.\nUse the `rule_id` to delete this rule later.","example":{"rule_id":"fw-a1b2c3d4-5678-90ab-cdef-123456789abc","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"192.0.2.100/32","port":5432,"description":"Office VPN connection","created_at":"2025-10-20T09:15:00Z"}},"FirewallRuleResponse":{"properties":{"rule_id":{"type":"string","title":"Rule Id","description":"Unique rule identifier (UUID)"},"db_name":{"type":"string","title":"Db Name","description":"Database name"},"db_type":{"type":"string","title":"Db Type","description":"Database type"},"ip_address":{"type":"string","title":"Ip Address","description":"IP address or CIDR block"},"port":{"type":"integer","title":"Port","description":"Database port"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional rule description"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["rule_id","db_name","db_type","ip_address","port","created_at"],"title":"FirewallRuleResponse","description":"Details of a firewall rule that was created or retrieved.\n\nThe `rule_id` uniquely identifies this rule and is needed to delete it later.","example":{"rule_id":"fw-a1b2c3d4-5678-90ab-cdef-123456789abc","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"192.0.2.100/32","port":5432,"description":"Office VPN connection","created_at":"2025-10-20T09:15:00Z"}},"SnapshotCreateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Name","description":"Short name for the snapshot (max 80 chars)"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"Longer description (max 500 chars)"}},"type":"object","title":"SnapshotCreateRequest","description":"Create a point-in-time snapshot of your database.\n\nSnapshots capture the current state of your database and can be used\nto restore data if needed. Add a description to remember why you created it.","example":{"description":"Before major schema migration"}},"SnapshotCreateResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"snapshot_id":{"type":"string","title":"Snapshot Id"},"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"size_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Size Mb"},"created_at":{"type":"string","title":"Created At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"message":{"type":"string","title":"Message"},"s3_backup":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"S3 Backup"},"offsite_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offsite Status"}},"type":"object","required":["success","snapshot_id","db_name","db_type","created_at","message"],"title":"SnapshotCreateResponse","description":"Your snapshot was created successfully.\n\nSave the `snapshot_id` - you'll need it to restore from this snapshot later.\nSnapshots are retained according to your backup policy.","example":{"success":true,"snapshot_id":"snap_20251126_143000_a1b2c3d4","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"created_at":"2025-11-26T14:30:00Z","description":"Before major schema migration","message":"Snapshot created successfully"}},"SnapshotInfo":{"properties":{"snapshot_id":{"type":"string","title":"Snapshot Id"},"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"size_mb":{"type":"number","title":"Size Mb"},"created_at":{"type":"string","title":"Created At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","title":"Status"},"location":{"type":"string","title":"Location","default":"local"},"snapshot_type":{"type":"string","title":"Snapshot Type","default":"manual"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"offsite_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offsite Status"}},"type":"object","required":["snapshot_id","db_name","db_type","size_mb","created_at","status"],"title":"SnapshotInfo","description":"Information about a database snapshot.\n\nShows when the snapshot was taken, its size, and current status.\nUse the `snapshot_id` to restore from this snapshot.","example":{"snapshot_id":"snap_20251126_143000_a1b2c3d4","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"created_at":"2025-11-26T14:30:00Z","description":"Before major schema migration","status":"completed"}},"SnapshotListResponse":{"properties":{"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"snapshots":{"items":{"$ref":"#/components/schemas/SnapshotInfo"},"type":"array","title":"Snapshots"}},"type":"object","required":["db_name","db_type","snapshots"],"title":"SnapshotListResponse","description":"All snapshots for a specific database.\n\nLists both manual snapshots you've created and automatic backups\nfrom your backup policy.","example":{"db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","snapshots":[{"snapshot_id":"snap_20251126_143000_a1b2c3d4","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"created_at":"2025-11-26T14:30:00Z","description":"Before major schema migration","status":"completed"},{"snapshot_id":"snap_20251125_020015_b2c3d4e5","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":248.2,"created_at":"2025-11-25T02:00:15Z","description":"Daily automated backup","status":"completed"}]}},"SnapshotRestoreRequest":{"properties":{"snapshot_id":{"type":"string","title":"Snapshot Id","description":"Snapshot ID to restore from"},"target_db_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Db Name","description":"Optional: Restore to new database name"}},"type":"object","required":["snapshot_id"],"title":"SnapshotRestoreRequest","description":"Restore your database from a snapshot.\n\nYou can either restore to the original database (overwrites current data)\nor create a new database from the snapshot by specifying `target_db_name`.","example":{"snapshot_id":"snap_20251126_143000_a1b2c3d4","target_db_name":"webapp_restored_db"}},"SnapshotRestoreResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"db_name":{"type":"string","title":"Db Name"},"snapshot_id":{"type":"string","title":"Snapshot Id"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","db_name","snapshot_id","message"],"title":"SnapshotRestoreResponse","description":"Your database was restored from the snapshot.\n\nIf you specified a `target_db_name`, a new database was created with the\nrestored data. Otherwise, the original database was overwritten.","example":{"success":true,"db_name":"cust_6dd1d906_pg_webapp_restored_db","snapshot_id":"snap_20251126_143000_a1b2c3d4","message":"Database restored successfully from snapshot"}},"ChatChoice":{"properties":{"index":{"type":"integer","title":"Index"},"message":{"$ref":"#/components/schemas/ResponseMessage"},"finish_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finish Reason"}},"type":"object","required":["index","message"],"title":"ChatChoice","description":"A single response from the model.\n\nMost requests return one choice. The `finish_reason` tells you why the\nmodel stopped: 'stop' means it finished naturally, 'length' means it\nhit the max_tokens limit.","example":{"index":0,"message":{"role":"assistant","content":"Hello! I'm doing well, thank you for asking. How can I assist you today?"},"finish_reason":"stop"}},"ChatRequest":{"properties":{"model":{"type":"string","title":"Model","description":"Model alias (e.g., 'mixtral-8x7b')"},"messages":{"items":{"$ref":"#/components/schemas/Message"},"type":"array","minItems":1,"title":"Messages"},"stream":{"type":"boolean","title":"Stream","description":"Enable SSE streaming","default":false},"temperature":{"anyOf":[{"type":"number","maximum":2.0,"minimum":0.0},{"type":"null"}],"title":"Temperature"},"max_tokens":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Max Tokens"},"top_p":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Top P"},"tools":{"anyOf":[{"items":{"type":"object"},"type":"array"},{"type":"null"}],"title":"Tools","description":"Tool definitions for function calling. Accepted for OpenAI compatibility, not yet implemented."},"tool_choice":{"anyOf":[{"type":"string"},{"type":"object"},{"type":"null"}],"title":"Tool Choice","description":"Controls tool selection. Accepted for OpenAI compatibility, not yet implemented."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Agent identifier for AI agents. Stored for logging only."},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"Preferred datacenter region for inference. Currently only 'oslo' is available."}},"additionalProperties":false,"type":"object","required":["model","messages"],"title":"ChatRequest","description":"Send a prompt to an AI model.\n\nThis endpoint is **OpenAI-compatible** - if you're already using the OpenAI SDK,\njust change the base URL to `https://llm.wayscloud.services` and it will work.\n\n**Quick start**: Set `model` to one of our available models (see `/v1/models`)\nand provide your messages. That's it!\n\n**Streaming**: Set `stream: true` for real-time responses (recommended for chat interfaces).","examples":[{"max_tokens":100,"messages":[{"content":"You are a helpful assistant.","role":"system"},{"content":"Hello!","role":"user"}],"model":"mixtral-8x7b","temperature":0.7},{"max_tokens":500,"messages":[{"content":"Explain quantum computing","role":"user"}],"model":"qwen3-235b-instruct","stream":true},{"agent_id":"ephemeral","max_tokens":1000,"messages":[{"content":"Write a Python function","role":"user"}],"model":"deepseek-v3","temperature":0.3,"tools":[]}],"example":{"model":"qwen3-235b-thinking","messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"What is the capital of Norway?"}],"temperature":0.7,"max_tokens":256}},"ChatResponse":{"properties":{"model_output_metadata":{"$ref":"#/components/schemas/ReasoningContainer","description":"Optional reasoning metadata returned by reasoning-capable models"}},"type":"object","required":["id","created","model","choices","usage"],"title":"ChatResponse","description":"The AI's response to your prompt.\n\nThe response includes the generated text in `choices[0].message.content`,\nalong with token usage for billing. Save the `id` if you need to reference\nthis request later.","example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"deepseek-r1","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":15,"completion_tokens":8,"total_tokens":23},"model_output_metadata":{"reasoning":{"summary":"Model evaluated alternative interpretations and selected the most likely answer.","depth":"standard","steps":["Parsed user intent","Evaluated context","Performed logical deduction","Selected final response"],"tokens":214}}}},"ContentPart":{"properties":{"type":{"type":"string","enum":["text","image_url"],"title":"Type","description":"Content type"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Text content (when type='text')"},"image_url":{"anyOf":[{"$ref":"#/components/schemas/ImageUrl"},{"type":"null"}],"description":"Image URL (when type='image_url')"}},"type":"object","required":["type"],"title":"ContentPart","description":"A single part of multimodal content"},"ErrorResponse":{"properties":{"error":{"type":"object","title":"Error"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Error details when something goes wrong.\n\nCheck `error.message` for a human-readable explanation and `error.code`\nfor programmatic handling.","example":{"error":{"message":"Model 'invalid-model' not found. Available models: mixtral-8x7b, deepseek-v3","type":"invalid_request_error","code":"model_not_found"}}},"ImageUrl":{"properties":{"url":{"type":"string","title":"Url","description":"Image URL or base64 data URI (data:image/png;base64,...)"},"detail":{"anyOf":[{"type":"string","enum":["auto","low","high"]},{"type":"null"}],"title":"Detail","description":"Image detail level for vision processing","default":"auto"}},"type":"object","required":["url"],"title":"ImageUrl","description":"Image URL for vision models"},"Message":{"properties":{"role":{"type":"string","enum":["system","user","assistant"],"title":"Role","description":"Message role: 'system' for instructions, 'user' for user input, 'assistant' for AI responses"},"content":{"anyOf":[{"type":"string"},{"items":{"$ref":"#/components/schemas/ContentPart"},"type":"array"}],"title":"Content","description":"Message content - string or list of content parts for multimodal"}},"type":"object","required":["role","content"],"title":"Message","description":"A single message in the conversation.\n\nBuild your conversation by adding messages with different roles:\n- `system`: Set the AI's behavior and personality\n- `user`: Your questions or prompts\n- `assistant`: Previous AI responses (for multi-turn conversations)\n\nFor vision models (e.g., qwen3-vl-8b), content can be a list of parts:\n```json\n{\n  \"role\": \"user\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"What's in this image?\"},\n    {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,...\"}}\n  ]\n}\n```","examples":[{"content":"Hello, how are you?","role":"user"},{"content":[{"text":"What's in this image?","type":"text"},{"image_url":{"url":"data:image/jpeg;base64,/9j/4AAQ..."},"type":"image_url"}],"role":"user"}]},"ModelOutputMetadata":{"properties":{"reasoning":{"anyOf":[{"$ref":"#/components/schemas/ReasoningMetadata"},{"type":"null"}],"description":"Reasoning metadata from chain-of-thought models"}},"type":"object","title":"ModelOutputMetadata","description":"Extended metadata returned by certain model types.\n\nCurrently includes reasoning traces from reasoning-capable models."},"ResponseMessage":{"properties":{"role":{"const":"assistant","title":"Role","default":"assistant"},"content":{"type":"string","title":"Content","description":"Response text content"}},"type":"object","required":["content"],"title":"ResponseMessage","description":"Response message - always has string content"},"Usage":{"properties":{"prompt_tokens":{"type":"integer","title":"Prompt Tokens","description":"Tokens in your input messages"},"completion_tokens":{"type":"integer","title":"Completion Tokens","description":"Tokens in the AI's response"},"total_tokens":{"type":"integer","title":"Total Tokens","description":"Total tokens (prompt + completion)"}},"type":"object","required":["prompt_tokens","completion_tokens","total_tokens"],"title":"Usage","description":"Token usage for this request.\n\nTokens are the basic unit of text that models process. Billing is based on\ntotal tokens used. Check `/v1/models` for per-token pricing.","example":{"prompt_tokens":25,"completion_tokens":42,"total_tokens":67}},"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["person","company"]},"name":{"type":"string"},"company_name":{"type":"string","nullable":true},"phone_number":{"type":"string","nullable":true},"email":{"type":"string","format":"email","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"notes":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","type":"person","name":"Ola Nordmann","company_name":"ACME AS","phone_number":"+4799999999","email":"ola@example.com","tags":["vip","newsletter"],"notes":"Key contact for project X","created_at":"2025-01-15T09:30:00Z","updated_at":"2025-01-15T09:30:00Z"}},"ContactCreate":{"type":"object","required":["name"],"properties":{"type":{"type":"string","enum":["person","company"],"default":"person"},"name":{"type":"string","minLength":1,"maxLength":255},"company_name":{"type":"string","maxLength":255},"phone_number":{"type":"string","maxLength":30,"description":"E.164 format recommended"},"email":{"type":"string","format":"email"},"tags":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}},"example":{"type":"person","name":"Ola Nordmann","phone_number":"+4799999999","email":"ola@example.com","tags":["vip"]}},"ContactGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"member_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-1234-5678-9abc-def012345678","name":"Newsletter Subscribers","description":"Customers who opted in for newsletter","member_count":150,"created_at":"2025-01-15T09:30:00Z"}},"ContactGroupCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":500}},"example":{"name":"VIP Customers","description":"High-value customers for priority support"}},"AlarmActionRequest":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Note","description":"Optional note","example":"Investigating root cause"}},"type":"object","title":"AlarmActionRequest","description":"Acknowledge, resolve, or act on an alarm"},"ChannelCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Channel name","example":"Ops Slack"},"channel_type":{"type":"string","title":"Channel Type","description":"Channel type: email, webhook, slack, teams, sms","example":"webhook"},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Channel-type-specific configuration","example":{"secret":"my-hmac-secret","url":"https://example.com/webhook"}},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether channel is active","default":true}},"type":"object","required":["name","channel_type","config"],"title":"ChannelCreateRequest","description":"Create a notification channel"},"ChannelUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"}},"type":"object","title":"ChannelUpdateRequest","description":"Update a notification channel"},"DatapointCreateRequest":{"properties":{"key":{"type":"string","maxLength":100,"minLength":1,"title":"Key","description":"Datapoint key (unique per customer)","example":"temperature"},"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Label","description":"Human-readable label","example":"Temperature"},"unit":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Unit","description":"Measurement unit","example":"°C"},"data_type":{"type":"string","title":"Data Type","description":"Data type: number, string, boolean","default":"number","example":"number"}},"type":"object","required":["key"],"title":"DatapointCreateRequest","description":"Define a named telemetry datapoint"},"DatapointUpdateRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Label"},"unit":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Unit"}},"type":"object","title":"DatapointUpdateRequest","description":"Update a datapoint definition"},"DeviceCreateRequest":{"properties":{"device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id","description":"Unique device identifier. Auto-generated UUID if omitted.","example":"sensor-001"},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Human-readable device name","example":"Temperature Sensor A"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Device description","example":"Office floor 3, room 301"},"device_type":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Device Type","description":"Device type or model","example":"ESP32-TempHumidity"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Custom key-value metadata","example":{"firmware":"1.2.0","location":"oslo"}}},"type":"object","required":["name"],"title":"DeviceCreateRequest","description":"Register a new IoT device"},"DeviceListResponse":{"properties":{"devices":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Devices","description":"List of device objects"},"total":{"type":"integer","title":"Total","description":"Total number of devices matching filter","example":42},"page":{"type":"integer","title":"Page","description":"Current page number","example":1},"page_size":{"type":"integer","title":"Page Size","description":"Items per page","example":20}},"type":"object","required":["devices","total","page","page_size"],"title":"DeviceListResponse","description":"Paginated list of devices"},"DeviceUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New device name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"New description"},"device_type":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Device Type","description":"New device type"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Replace custom metadata"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Activate or deactivate device"}},"type":"object","title":"DeviceUpdateRequest","description":"Update device information"},"GroupCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Group name","example":"Factory Floor A"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Group description"}},"type":"object","required":["name"],"title":"GroupCreateRequest","description":"Create a device group"},"GroupUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New group name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"New description"}},"type":"object","title":"GroupUpdateRequest","description":"Update a device group"},"PolicyCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Policy name","example":"Critical to Ops"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"severity_filter":{"items":{"type":"string"},"type":"array","title":"Severity Filter","description":"Which alarm severities to match","default":["critical","warning","info"]},"rule_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Rule Ids","description":"Specific rule IDs to match (null = all rules)"},"scope_type":{"type":"string","title":"Scope Type","description":"Scope: fleet, device, group, profile","default":"fleet"},"scope_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scope Ids","description":"IDs within scope (null for fleet)"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"Alarm events: triggered, acknowledged, resolved","default":["triggered"]},"channel_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Channel Ids","description":"Channel IDs to deliver to"},"cooldown_seconds":{"type":"integer","maximum":86400.0,"minimum":0.0,"title":"Cooldown Seconds","description":"Minimum seconds between notifications for same alarm","default":300},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether policy is active","default":true}},"type":"object","required":["name","channel_ids"],"title":"PolicyCreateRequest","description":"Create a notification policy"},"PolicyUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"severity_filter":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Severity Filter"},"event_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Event Types"},"channel_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Channel Ids"},"cooldown_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":0.0},{"type":"null"}],"title":"Cooldown Seconds"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"}},"type":"object","title":"PolicyUpdateRequest","description":"Update a notification policy"},"ProfileCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Profile name","example":"Default Sensor Config"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Profile description"},"expected_topics":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Expected Topics","description":"MQTT topics the device should publish to","example":["sensors/temperature","sensors/humidity"]},"reporting_interval_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":1.0},{"type":"null"}],"title":"Reporting Interval Seconds","description":"Expected reporting interval in seconds","example":60},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Profile metadata"}},"type":"object","required":["name"],"title":"ProfileCreateRequest","description":"Create a device profile (configuration template)"},"ProfileUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"expected_topics":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Expected Topics"},"reporting_interval_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":1.0},{"type":"null"}],"title":"Reporting Interval Seconds"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"ProfileUpdateRequest","description":"Update a device profile"},"RuleCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Rule name","example":"Offline Alert"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Rule description"},"rule_type":{"type":"string","title":"Rule Type","description":"Rule type: missing_data, offline, threshold, message_rate, reconnect_rate","example":"offline"},"scope_type":{"type":"string","title":"Scope Type","description":"Scope: fleet, group, profile, device","default":"fleet","example":"fleet"},"scope_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Device Id","description":"Device ID when scope_type=device"},"scope_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Group Id","description":"Group ID when scope_type=group"},"scope_profile_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Profile Id","description":"Profile ID when scope_type=profile"},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Rule-type-specific configuration","example":{"silence_seconds":300}},"severity":{"type":"string","title":"Severity","description":"Alarm severity: critical, warning, info","default":"warning","example":"critical"},"actions":{"items":{"type":"string"},"type":"array","title":"Actions","description":"Actions on trigger: create_alarm, send_webhook, mark_unhealthy","default":["create_alarm"]},"cooldown_seconds":{"type":"integer","maximum":86400.0,"minimum":60.0,"title":"Cooldown Seconds","description":"Minimum seconds between alarm triggers","default":300,"example":300},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether rule is active","default":true},"auto_resolve":{"type":"boolean","title":"Auto Resolve","description":"Auto-resolve alarm when condition clears","default":false}},"type":"object","required":["name","rule_type","config"],"title":"RuleCreateRequest","description":"Create an alarm rule"},"RuleUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"},"actions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Actions"},"cooldown_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":60.0},{"type":"null"}],"title":"Cooldown Seconds"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"},"auto_resolve":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Resolve"}},"type":"object","title":"RuleUpdateRequest","description":"Update an alarm rule"},"IoTDevice":{"type":"object","properties":{"device_id":{"type":"string","description":"Unique device identifier"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"device_type":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"is_online":{"type":"boolean"},"last_seen":{"type":"string","format":"date-time","nullable":true},"connection_status":{"type":"string","enum":["connected","disconnected"]},"stability_score":{"type":"integer","nullable":true},"firmware_version":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"metadata":{"type":"object","nullable":true},"messages_24h":{"type":"integer","nullable":true},"battery_level":{"type":"integer","nullable":true},"signal_strength":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"device_id":"sensor-001","name":"Temperature Sensor A","device_type":"ESP32","is_active":true,"is_online":true,"last_seen":"2026-03-30T09:55:00Z","connection_status":"connected","stability_score":95,"firmware_version":"2.1.0","tags":["production","floor-3"],"metadata":{"location":"Building A"},"messages_24h":1440,"battery_level":87,"signal_strength":-65,"created_at":"2026-01-15T10:00:00Z"}},"IoTDeviceList":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/IoTDevice"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"},"has_more":{"type":"boolean"}},"example":{"devices":[{"device_id":"sensor-001","name":"Temperature Sensor A","is_active":true,"is_online":true,"last_seen":"2026-03-30T09:55:00Z"},{"device_id":"sensor-002","name":"Humidity Sensor B","is_active":true,"is_online":true}],"total":42,"page":1,"page_size":20,"has_more":true}},"IoTDeviceHealth":{"type":"object","properties":{"device_id":{"type":"string"},"health_score":{"type":"integer","minimum":0,"maximum":100},"health_status":{"type":"string","enum":["healthy","degraded","unhealthy","offline","unknown"]},"components":{"type":"object"},"checked_at":{"type":"string","format":"date-time"}},"example":{"device_id":"sensor-001","health_score":92,"health_status":"healthy","components":{"uptime":100,"alarm_burden":95,"connection_stability":90,"telemetry_freshness":85},"checked_at":"2026-03-30T10:00:00Z"}},"IoTDeviceTags":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"example":{"tags":["production","floor-3","critical"]}},"IoTDeviceTagsAdded":{"type":"object","properties":{"added":{"type":"array","items":{"type":"string"}}},"example":{"added":["new-tag-1","new-tag-2"]}},"IoTGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-5678-9abc-def0-123456789abc","name":"Floor 3 Sensors","description":"All sensors on floor 3","color":"#FF5733","created_at":"2026-01-20T14:00:00Z","updated_at":"2026-03-20T14:22:00Z"}},"IoTGroupList":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/IoTGroup"}}},"example":{"groups":[{"id":"a1b2c3d4-5678-9abc-def0-123456789abc","name":"Floor 3 Sensors","description":"All sensors on floor 3","color":"#FF5733"}]}},"IoTGroupDevicesAdded":{"type":"object","properties":{"count":{"type":"integer"},"added":{"type":"array","items":{"type":"string"}}},"example":{"count":3,"added":["sensor-001","sensor-002","sensor-003"]}},"IoTProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"expected_topics":{"type":"array","items":{"type":"string"},"nullable":true},"reporting_interval_seconds":{"type":"integer","nullable":true},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"b2c3d4e5-6789-0abc-def1-234567890bcd","name":"Temperature Sensor Profile","description":"Standard temperature/humidity sensors","expected_topics":["devices/+/sensors/temperature","devices/+/sensors/humidity"],"reporting_interval_seconds":60,"metadata":{"sensor_type":"DHT22"},"created_at":"2026-01-10T12:00:00Z","updated_at":"2026-03-20T14:22:00Z"}},"IoTProfileList":{"type":"object","properties":{"profiles":{"type":"array","items":{"$ref":"#/components/schemas/IoTProfile"}}},"example":{"profiles":[{"id":"b2c3d4e5-6789-0abc-def1-234567890bcd","name":"Temperature Sensor Profile","reporting_interval_seconds":60}]}},"IoTProfileApplyResult":{"type":"object","properties":{"profile_id":{"type":"string"},"device_id":{"type":"string"},"applied":{"type":"boolean"}},"example":{"profile_id":"prof-abc123","device_id":"sensor-001","applied":true}},"IoTRule":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"rule_type":{"type":"string","enum":["offline","missing_data","threshold","message_rate","reconnect_rate"]},"scope_type":{"type":"string","enum":["fleet","device","group","profile"]},"scope_device_id":{"type":"string","nullable":true},"scope_group_id":{"type":"string","nullable":true},"scope_profile_id":{"type":"string","nullable":true},"config":{"type":"object"},"severity":{"type":"string","enum":["critical","warning","info"]},"actions":{"type":"array","items":{"type":"string"}},"cooldown_seconds":{"type":"integer"},"is_enabled":{"type":"boolean"},"auto_resolve":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_evaluated_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"c3d4e5f6-7890-1abc-def2-345678901cde","name":"High temperature alert","rule_type":"threshold","scope_type":"fleet","config":{"datapoint_id":"dp-abc123","operator":"gt","value":35,"duration_seconds":300},"severity":"warning","actions":["create_alarm"],"cooldown_seconds":300,"is_enabled":true,"auto_resolve":false,"created_at":"2026-02-01T10:00:00Z","updated_at":"2026-03-20T14:22:00Z"}},"IoTRuleList":{"type":"object","properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/IoTRule"}}},"example":{"rules":[{"id":"c3d4e5f6-7890-1abc-def2-345678901cde","name":"High temperature alert","rule_type":"threshold","severity":"warning","is_enabled":true}]}},"IoTAlarm":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"device_id":{"type":"string"},"rule_id":{"type":"string","format":"uuid"},"rule_name":{"type":"string","nullable":true},"severity":{"type":"string","enum":["critical","warning","info"]},"title":{"type":"string"},"message":{"type":"string"},"status":{"type":"string","enum":["active","acknowledged","resolved","suppressed","expired"]},"triggered_at":{"type":"string","format":"date-time"},"occurrence_count":{"type":"integer"},"acknowledged_at":{"type":"string","format":"date-time","nullable":true},"acknowledged_by":{"type":"string","nullable":true},"resolved_at":{"type":"string","format":"date-time","nullable":true},"resolution_note":{"type":"string","nullable":true},"context":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"d4e5f6a7-8901-2bcd-ef34-567890123def","device_id":"sensor-001","rule_id":"c3d4e5f6-7890-1abc-def2-345678901cde","rule_name":"High temperature alert","severity":"warning","title":"Device offline: sensor-001","message":"Device has been offline for 5 minutes","status":"active","triggered_at":"2026-03-30T14:00:00Z","occurrence_count":3,"acknowledged_at":null,"resolved_at":null,"created_at":"2026-03-30T14:00:00Z"}},"IoTAlarmList":{"type":"object","properties":{"alarms":{"type":"array","items":{"$ref":"#/components/schemas/IoTAlarm"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"}},"example":{"alarms":[{"id":"d4e5f6a7-8901-2bcd-ef34-567890123def","device_id":"sensor-001","severity":"warning","title":"Device offline: sensor-001","status":"active","triggered_at":"2026-03-30T14:00:00Z","occurrence_count":3}],"total":12,"page":1,"page_size":50}},"IoTAlarmSummary":{"type":"object","properties":{"by_status":{"type":"object"},"by_severity":{"type":"object"},"active_total":{"type":"integer"}},"example":{"by_status":{"active":8,"acknowledged":3,"resolved":45},"by_severity":{"critical":2,"warning":7,"info":3},"active_total":11}},"IoTAlarmTimeline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"event_type":{"type":"string"},"actor":{"type":"string"},"note":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"example":[{"id":1,"event_type":"triggered","actor":"rule_engine","note":null,"metadata":{"severity":"warning"},"created_at":"2026-03-30T14:00:00Z"},{"id":2,"event_type":"acknowledged","actor":"operator@example.com","note":"Investigating","metadata":null,"created_at":"2026-03-30T14:15:00Z"}]},"IoTDatapoint":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"label":{"type":"string","nullable":true},"source_topic":{"type":"string","nullable":true},"json_path":{"type":"string","nullable":true},"data_type":{"type":"string","enum":["number","string","boolean"]},"unit":{"type":"string","nullable":true},"aggregation_method":{"type":"string","nullable":true},"is_chartable":{"type":"boolean"},"is_alertable":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"e5f6a7b8-9012-3cde-f456-7890123456ef","name":"temperature","label":"Temperature","unit":"°C","data_type":"number","aggregation_method":"avg","is_chartable":true,"is_alertable":true,"created_at":"2026-01-10T12:00:00Z"}},"IoTDatapointList":{"type":"object","properties":{"datapoints":{"type":"array","items":{"$ref":"#/components/schemas/IoTDatapoint"}}},"example":{"datapoints":[{"id":"e5f6a7b8-9012-3cde-f456-7890123456ef","name":"temperature","label":"Temperature","unit":"°C","data_type":"number"}]}},"IoTTelemetryData":{"type":"array","items":{"type":"object","properties":{"datapoint_id":{"type":"string"},"name":{"type":"string"},"label":{"type":"string","nullable":true},"unit":{"type":"string","nullable":true},"data_type":{"type":"string"},"measured_at":{"type":"string","format":"date-time"},"value":{}}},"example":[{"datapoint_id":"dp-abc123","name":"temperature","label":"Temperature","unit":"°C","data_type":"number","measured_at":"2026-03-30T09:55:00Z","value":22.5},{"datapoint_id":"dp-def456","name":"humidity","label":"Humidity","unit":"%","data_type":"number","measured_at":"2026-03-30T09:55:00Z","value":45.2}]},"IoTChannel":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"channel_type":{"type":"string","enum":["email","webhook","slack","teams","sms"]},"config":{"type":"object"},"is_enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"f6a7b8c9-0123-4def-5678-901234567890","name":"Ops Webhook","channel_type":"webhook","config":{"url":"https://example.com/webhooks/iot"},"is_enabled":true,"created_at":"2026-02-01T10:00:00Z"}},"IoTChannelList":{"type":"object","properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/IoTChannel"}}},"example":{"channels":[{"id":"f6a7b8c9-0123-4def-5678-901234567890","name":"Ops Webhook","channel_type":"webhook","is_enabled":true}]}},"IoTChannelTestResult":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"channel_id":{"type":"string"}},"example":{"success":true,"message":"Test notification sent successfully","channel_id":"ch-abc123"}},"IoTPolicy":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"severity_filter":{"type":"array","items":{"type":"string"}},"event_types":{"type":"array","items":{"type":"string"}},"channel_ids":{"type":"array","items":{"type":"string"}},"cooldown_seconds":{"type":"integer"},"is_enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"a7b8c9d0-1234-5ef6-7890-123456789012","name":"Critical alerts to Slack","severity_filter":["critical"],"event_types":["triggered"],"channel_ids":["ch-abc123"],"cooldown_seconds":300,"is_enabled":true,"created_at":"2026-02-01T10:00:00Z"}},"IoTPolicyList":{"type":"object","properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/IoTPolicy"}}},"example":{"policies":[{"id":"a7b8c9d0-1234-5ef6-7890-123456789012","name":"Critical alerts to Slack","severity_filter":["critical"],"is_enabled":true}]}},"IoTDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"channel_type":{"type":"string"},"status":{"type":"string","enum":["queued","delivered","failed"]},"alarm_id":{"type":"string","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"error_message":{"type":"string","nullable":true}},"example":{"id":"b8c9d0e1-2345-6f78-9012-345678901234","channel_type":"webhook","status":"delivered","alarm_id":"alm-abc123","sent_at":"2026-03-30T14:05:00Z","error_message":null}},"IoTDeliveryList":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/IoTDelivery"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"example":{"deliveries":[{"id":"b8c9d0e1-2345-6f78-9012-345678901234","channel_type":"webhook","status":"delivered","sent_at":"2026-03-30T14:05:00Z"}],"total":25,"limit":50,"offset":0}},"IoTCredentials":{"type":"object","properties":{"mqtt_broker":{"type":"object","properties":{"host":{"type":"string"},"port":{"type":"integer"},"tls_port":{"type":"integer"},"websocket_port":{"type":"integer"},"protocol":{"type":"string"}}}},"example":{"mqtt_broker":{"host":"mqtt.wayscloud.services","port":1883,"tls_port":8883,"websocket_port":8083,"protocol":"mqtt"}}},"IoTSubscription":{"type":"object","properties":{"plan":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","inactive","suspended"]},"device_limit":{"type":"integer"},"device_count":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}},"example":{"plan":"iot-50","status":"active","device_limit":50,"device_count":12,"created_at":"2026-01-01T00:00:00Z"}},"IoTUsage":{"type":"object","properties":{"messages_today":{"type":"integer"},"messages_this_month":{"type":"integer"},"data_points_today":{"type":"integer"},"data_points_this_month":{"type":"integer"}},"example":{"messages_today":4521,"messages_this_month":142000,"data_points_today":18000,"data_points_this_month":560000}},"IoTStats":{"type":"object","properties":{"total_devices":{"type":"integer"},"active_devices":{"type":"integer"},"connected_now":{"type":"integer"},"messages_24h":{"type":"integer"},"data_points_24h":{"type":"integer"},"active_alarms":{"type":"integer"}},"example":{"total_devices":42,"active_devices":39,"connected_now":38,"messages_24h":62000,"data_points_24h":248000,"active_alarms":3}},"WatchStateResponse":{"type":"object","description":"Watch state for an event. Contains both the internal lifecycle `status` and the viewer-facing `watch_state`. The watch page should react to `watch_state`, not `status`.","properties":{"event_id":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a","description":"Unique event identifier"},"public_id":{"type":"string","format":"uuid","example":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","description":"Global event identity used by Meil Live and branded audience URLs"},"slug":{"type":"string","example":"product-demo","description":"URL-safe event identifier used in watch and embed URLs"},"name":{"type":"string","example":"Product Demo","description":"Event display name"},"description":{"type":"string","nullable":true,"example":"Live demo of new release features","description":"Optional event description"},"status":{"type":"string","enum":["scheduled","ready","rehearsal","live","ending","ended","processing","failed","cancelled"],"example":"live","description":"Internal lifecycle state. For UI logic, use `watch_state` instead."},"watch_state":{"type":"string","enum":["not_started","access_required","live","rehearsal_hidden","processing_replay","replay_available","ended_no_replay","cancelled"],"example":"live","description":"Canonical viewer-facing UI state. The watch page should render based on this field."},"visibility_mode":{"type":"string","enum":["public","token","password","tenant_only"],"example":"public","description":"Access control model for this event"},"requires_auth":{"type":"boolean","example":false,"description":"Whether the viewer must verify access (password, token, or login) before playback URLs are returned"},"auth_type":{"type":"string","nullable":true,"enum":["password","token","tenant_only"],"example":null,"description":"Which auth method is required, if any. Null for public events."},"scheduled_start_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-04-10T12:00:00+00:00","description":"Planned start time (ISO 8601)"},"scheduled_end_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-04-10T13:30:00+00:00","description":"Planned end time (ISO 8601)"},"timezone":{"type":"string","example":"Europe/Oslo","description":"IANA timezone for display purposes"},"countdown_enabled":{"type":"boolean","example":false,"description":"Whether to show a countdown timer before the event starts"},"playback_mode":{"type":"string","enum":["live","replay","none"],"example":"live","description":"Which playback path is currently active"},"live_playback":{"type":"object","nullable":true,"description":"Live stream playback info. Present only when the event is live and the viewer has access.","properties":{"hls_url":{"type":"string","format":"uri","example":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","description":"HLS playback URL. Does not expire while event is live."},"protocol":{"type":"string","enum":["hls"],"example":"hls"}}},"replay_playback":{"type":"object","nullable":true,"description":"Replay playback info. Present only when the event has ended with a published replay and the viewer has access.","properties":{"url":{"type":"string","format":"uri","example":"https://s3.wayscloud.services/media-events/tenant/.../recording.mp4?X-Amz-Expires=604800","description":"Presigned replay URL. Re-resolve the event for a fresh URL after expiry."},"protocol":{"type":"string","enum":["mp4","hls"],"example":"mp4"},"duration_seconds":{"type":"integer","nullable":true,"example":5255,"description":"Total replay duration in seconds"},"expires_at":{"type":"string","format":"date-time","example":"2026-04-17T13:28:45+00:00","description":"When this presigned URL expires. Typically 7 days."}}},"embed_enabled":{"type":"boolean","example":true,"description":"Whether iframe embedding is permitted for this event"},"banner_url":{"type":"string","nullable":true,"example":null,"description":"Optional banner image URL for the watch page"},"logo_url":{"type":"string","nullable":true,"example":null,"description":"Optional logo image URL for the watch page"}}},"VerifyAccessResponse":{"type":"object","description":"Response from password or token verification. Contains an access grant and the current playback information.","properties":{"verified":{"type":"boolean","example":true,"description":"Whether verification succeeded"},"event_id":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a","description":"Internal event identifier"},"public_id":{"type":"string","format":"uuid","example":"4ee07f75-9be5-4ed6-a43f-4ad9319c6436","description":"Global event identity used by Meil Live and branded audience URLs"},"access_grant":{"type":"string","example":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","description":"Short-lived access grant. Pass this to consume-grant when playback starts."},"access_grant_expires_at":{"type":"string","format":"date-time","example":"2026-04-10T18:00:00+00:00","description":"When the access grant expires. Viewer must re-verify after expiry."},"watch_state":{"type":"string","enum":["not_started","access_required","live","rehearsal_hidden","processing_replay","replay_available","ended_no_replay","cancelled"],"example":"live","description":"Current watch state at time of verification"},"playback_mode":{"type":"string","enum":["live","replay","none"],"example":"live","description":"Which playback path is active"},"live_playback":{"type":"object","nullable":true,"description":"Live stream info, if event is live","properties":{"hls_url":{"type":"string","format":"uri","example":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8"},"protocol":{"type":"string","example":"hls"}}},"replay_playback":{"type":"object","nullable":true,"description":"Replay info, if replay is available","properties":{"url":{"type":"string","format":"uri","example":"https://s3.wayscloud.services/media-events/tenant/.../recording.mp4?X-Amz-Expires=604800"},"protocol":{"type":"string","example":"mp4"},"duration_seconds":{"type":"integer","nullable":true,"example":5255},"expires_at":{"type":"string","format":"date-time","example":"2026-04-17T18:00:00+00:00","description":"When the presigned replay URL expires"}}}}},"ConsumeGrantResponse":{"type":"object","description":"Result of consuming an access grant when playback starts.","properties":{"consumed":{"type":"boolean","example":true,"description":"Whether the grant was consumed"},"already_consumed":{"type":"boolean","example":false,"description":"True if this grant was already consumed in a previous call. Token use_count is not incremented again."}}},"LiveEventCreateRequest":{"type":"object","required":["name"],"description":"Request to create a new live event.","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"example":"Product Demo","description":"Event display name"},"slug":{"type":"string","maxLength":100,"example":"product-demo","description":"URL-safe slug. Auto-generated if omitted. Unique per customer."},"description":{"type":"string","maxLength":5000,"example":"Live demo of new release features","description":"Optional event description"},"scheduled_start_at":{"type":"string","format":"date-time","example":"2026-04-10T12:00:00Z","description":"Planned start time (ISO 8601)"},"scheduled_end_at":{"type":"string","format":"date-time","example":"2026-04-10T13:30:00Z","description":"Planned end time"},"timezone":{"type":"string","default":"Europe/Oslo","example":"Europe/Oslo"},"visibility_mode":{"type":"string","enum":["public","token","password","tenant_only"],"default":"public","example":"public"},"password":{"type":"string","minLength":4,"description":"Required when visibility_mode is password"},"record_enabled":{"type":"boolean","default":true,"example":true},"replay_enabled":{"type":"boolean","default":true,"example":true},"rehearsal_enabled":{"type":"boolean","default":true,"example":true},"auto_start_on_ingest":{"type":"boolean","default":false,"example":false,"description":"Auto-transition to live when encoder connects"},"embed_enabled":{"type":"boolean","default":true,"example":true},"max_bitrate_kbps":{"type":"integer","minimum":500,"maximum":25000,"description":"Max ingest bitrate (plan-dependent)"},"retention_days":{"type":"integer","minimum":1,"maximum":365,"description":"Recording retention period"}}},"LiveEventUpdateRequest":{"type":"object","description":"Update event properties. All fields optional. Only allowed when status is draft, scheduled, or ready.","properties":{"name":{"type":"string","maxLength":255,"example":"Updated Demo Title"},"description":{"type":"string","maxLength":5000},"scheduled_start_at":{"type":"string","format":"date-time"},"scheduled_end_at":{"type":"string","format":"date-time"},"visibility_mode":{"type":"string","enum":["public","token","password","tenant_only"]},"password":{"type":"string","minLength":4,"description":"Required when changing to password visibility"},"record_enabled":{"type":"boolean"},"replay_enabled":{"type":"boolean"},"embed_enabled":{"type":"boolean"},"auto_start_on_ingest":{"type":"boolean"}}},"LiveEventTokenCreateRequest":{"type":"object","description":"Create a watch token for a token-gated event.","properties":{"label":{"type":"string","maxLength":100,"example":"VIP Attendees","description":"Human-readable label"},"max_uses":{"type":"integer","minimum":1,"maximum":100000,"example":50,"description":"Max playback sessions (consumed via access-grant model)"},"expires_at":{"type":"string","format":"date-time","example":"2026-04-10T18:00:00Z","description":"Token expiry"}}}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Service API key for server-to-server calls. Use either X-API-Key or Authorization: Bearer <key>."},"AccountAuth":{"type":"http","scheme":"bearer","bearerFormat":"wayscloud-pat","description":"Personal Access Token for account management: wayscloud_pat_xxx_yyy"},"ServiceAuth":{"type":"http","scheme":"bearer","bearerFormat":"wayscloud-service-key","description":"Service API key for provisioning and using services"},"DynDnsBasicAuth":{"type":"http","scheme":"basic","description":"Dynway router credentials. Username is the Dynway token ID; password is the router password returned once when the token is created or rotated."},"AwsSigV4":{"type":"apiKey","in":"header","name":"Authorization","description":"AWS Signature V4 for S3-compatible storage. Use standard AWS SDKs."}}}}