{ "openapi": "3.1.0", "info": { "title": "Scanopy API", "description": "\nNetwork topology discovery and visualization API.\n\n## Authentication\n\nTwo authentication methods are supported:\n\n| Method | Header | Use Case |\n|--------|--------|----------|\n| User API key | `Authorization: Bearer scp_u_...` | Programmatic access, integrations |\n| Session cookie | `Cookie: session_id=...` | Web UI (via `/api/auth/login`) |\n\nUser API keys require your organization to have API access enabled. Create keys at **Platform > API Keys**.\n\n## Rate Limiting\n\nLimit: 300 requests/minute\n\nBurst: 150\n\nResponse headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`\n\nWhen rate limited, you'll receive HTTP `429 Too Many Requests` with a `Retry-After` header.\n\n## Pagination\n\nList endpoints support pagination via query parameters:\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `limit` | integer | 50 | Maximum results to return (1-1000). Use 0 for no limit. |\n| `offset` | integer | 0 | Number of results to skip |\n\nExample: `GET /api/v1/hosts?limit=10&offset=20`\n\n## Response Format\n\nAll responses use a standard envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": {\n \"api_version\": 1,\n \"server_version\": \"0.17.1\"\n }\n}\n```\n\n**Paginated list responses** include pagination metadata:\n\n```json\n{\n \"success\": true,\n \"data\": [ ... ],\n \"meta\": {\n \"api_version\": 1,\n \"server_version\": \"0.17.1\",\n \"pagination\": {\n \"total_count\": 142,\n \"limit\": 50,\n \"offset\": 0,\n \"has_more\": true\n }\n }\n}\n```\n\n| Field | Description |\n|-------|-------------|\n| `total_count` | Total items matching your query (ignoring pagination) |\n| `limit` | Applied limit (your request or default) |\n| `offset` | Applied offset |\n| `has_more` | `true` if more results exist beyond this page |\n\n**Error responses** include an `error` field instead of `data`:\n\n```json\n{\n \"success\": false,\n \"error\": \"Resource not found\",\n \"meta\": { ... }\n}\n```\n\n**Common status codes:** `400` validation error, `401` unauthorized, `403` forbidden, `404` not found, `409` conflict, `429` rate limited.\n\n## Versioning\n\nEndpoints are prefixed with `/api/v1/`. The API version is an integer (`api_version: 1`) returned in every response, versioned independently from the application. Check `GET /api/version` for current versions.\n\n**While Scanopy is pre-v1.0 (current: 0.17.1)**, the API should be considered unstable. Breaking changes may be introduced in any release without incrementing the API version. We recommend pinning to a specific Scanopy release if you depend on API stability, and reviewing the [changelog](/changelog) before upgrading. After Scanopy reaches v1.0, breaking API changes will only occur with an API version increment.\n\n## Multi-Tenancy\n\nResources are scoped to your **organization** and **network(s)**:\n\n- You can only access entities within your organization\n- Network-level entities (hosts, services, etc.) are filtered to networks you have access to\n- Use `?network_id=` to filter list endpoints to a specific network\n- API keys can be scoped to a subset of your accessible networks\n", "license": { "name": "Dual (AGPL3.0, Commercial License Available)" }, "version": "1" }, "paths": { "/api/auth/check-email": { "post": { "tags": [ "auth", "internal" ], "operationId": "check_email", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckEmailRequest" } } }, "required": true }, "responses": { "200": { "description": "Email is available", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "409": { "description": "Email already in use", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/forgot-password": { "post": { "tags": [ "auth", "internal" ], "operationId": "forgot_password", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ForgotPasswordRequest" } } }, "required": true }, "responses": { "200": { "description": "Password reset email sent", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } } } }, "/api/auth/login": { "post": { "tags": [ "auth", "internal" ], "operationId": "login", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } }, "required": true }, "responses": { "200": { "description": "Login successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "401": { "description": "Invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Login forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/logout": { "post": { "tags": [ "auth", "internal" ], "operationId": "logout", "responses": { "200": { "description": "Logout successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } } } }, "/api/auth/me": { "post": { "tags": [ "auth", "internal" ], "operationId": "get_current_user", "responses": { "200": { "description": "Current user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/oidc/{slug}/unlink": { "post": { "tags": [ "auth", "internal" ], "operationId": "unlink_oidc_account", "parameters": [ { "name": "slug", "in": "path", "description": "OIDC provider slug", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OIDC account unlinked", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Blocked in demo mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Provider not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/onboarding-state": { "get": { "tags": [ "auth", "internal" ], "summary": "Get current onboarding state from session", "operationId": "onboarding_state", "responses": { "200": { "description": "Onboarding state", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_OnboardingStateResponse" } } } } } } }, "/api/auth/onboarding-step": { "post": { "tags": [ "auth", "internal" ], "summary": "Store onboarding step in session", "operationId": "onboarding_step", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OnboardingStepRequest" } } }, "required": true }, "responses": { "200": { "description": "Step saved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } } } }, "/api/auth/register": { "post": { "tags": [ "auth", "internal" ], "operationId": "register", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterRequest" } } }, "required": true }, "responses": { "200": { "description": "User registered successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Registration disabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Email already exists", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/request-email-change": { "post": { "tags": [ "auth", "internal" ], "operationId": "request_email_change", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestEmailChangeRequest" } } }, "required": true }, "responses": { "200": { "description": "Verification email sent to new address", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/resend-verification": { "post": { "tags": [ "auth", "internal" ], "operationId": "resend_verification", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResendVerificationRequest" } } }, "required": true }, "responses": { "200": { "description": "Verification email sent", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Invalid request or already verified", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "429": { "description": "Rate limited", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/reset-password": { "post": { "tags": [ "auth", "internal" ], "operationId": "reset_password", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResetPasswordRequest" } } }, "required": true }, "responses": { "200": { "description": "Password reset successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "400": { "description": "Invalid or expired token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/setup": { "post": { "tags": [ "auth", "internal" ], "summary": "Store pre-registration setup data (org name, networks, seed preference) in session", "operationId": "setup", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetupRequest" } } }, "required": true }, "responses": { "200": { "description": "Setup data stored", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_SetupResponse" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/update": { "post": { "tags": [ "auth", "internal" ], "operationId": "update_password_auth", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdatePasswordRequest" } } }, "required": true }, "responses": { "200": { "description": "Password updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Blocked in demo mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/auth/verify-email": { "post": { "tags": [ "auth", "internal" ], "operationId": "verify_email", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifyEmailRequest" } } }, "required": true }, "responses": { "200": { "description": "Email verified successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "400": { "description": "Invalid or expired token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/billing/cancel": { "post": { "tags": [ "billing", "internal" ], "summary": "Cancel subscription", "description": "In-app cancel modal endpoint. Sets Stripe `cancel_at` to the current\nperiod end (via Stripe's `MaxPeriodEnd` sentinel), stashes the canonical\nScanopy reason in subscription metadata, returns the period end so the\nmodal can render the retention disclosure.", "operationId": "cancel_subscription", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CancelSubscriptionRequest" } } }, "required": true }, "responses": { "200": { "description": "Cancellation initiated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_CancelSubscriptionResponse" } } } }, "400": { "description": "No active subscription or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/cancel/apply-discount": { "post": { "tags": [ "billing", "internal" ], "summary": "Apply the discount save offer", "description": "Applies the configured Stripe coupon to the subscription. Returns 400\nwhen `STRIPE_SAVE_OFFER_COUPON_ID` is unset.", "operationId": "apply_discount_save_offer", "responses": { "200": { "description": "Discount applied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "Discount not configured or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/change-plan": { "post": { "tags": [ "billing", "internal" ], "summary": "Change billing plan", "description": "Upgrades or downgrades the organization's billing plan.", "operationId": "change_plan", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChangePlanRequest" } } }, "required": true }, "responses": { "200": { "description": "Plan change initiated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "Invalid plan or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/change-plan/preview": { "get": { "tags": [ "billing", "internal" ], "summary": "Preview plan change (shows overage counts)", "operationId": "preview_plan_change", "parameters": [ { "name": "plan", "in": "query", "description": "Target plan (JSON)", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Plan change preview", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_ChangePlanPreview" } } } }, "400": { "description": "Billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/checkout": { "post": { "tags": [ "billing", "internal" ], "summary": "Create a checkout session", "operationId": "create_checkout_session", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCheckoutRequest" } } }, "required": true }, "responses": { "200": { "description": "Checkout session URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "Invalid plan or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/extend-trial": { "post": { "tags": [ "billing", "internal" ], "summary": "Self-serve trial extend (+7 days, once per org lifetime)", "operationId": "extend_trial", "responses": { "200": { "description": "Trial extended", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "Ineligible or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/finalize-payment-method": { "post": { "tags": [ "billing", "internal" ], "summary": "Finalize a client-confirmed SetupIntent (set the card as default)", "operationId": "finalize_payment_method", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FinalizePaymentMethodRequest" } } }, "required": true }, "responses": { "200": { "description": "Payment method finalized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Billing not enabled or SetupIntent invalid", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/inquiry": { "post": { "tags": [ "billing", "internal" ], "summary": "Submit enterprise plan inquiry", "description": "Updates Brevo contact/company with inquiry data, creates a deal, and\ntracks an event for automation triggers. Requires authentication to\nlink the inquiry to an organization.", "operationId": "submit_enterprise_inquiry", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnterpriseInquiryRequest" } } }, "required": true }, "responses": { "200": { "description": "Inquiry submitted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Invalid request or Brevo not configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "401": { "description": "Authentication required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/pause": { "post": { "tags": [ "billing", "internal" ], "summary": "Pause subscription billing", "description": "Pauses billing for a 30/60/90 day window. Eligibility: rolling 6-month\ncooldown anchored on the org's `last_paused_at`.", "operationId": "pause_subscription", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PauseSubscriptionRequest" } } }, "required": true }, "responses": { "200": { "description": "Subscription paused", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "Ineligible or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/payment-method-setup-intent": { "post": { "tags": [ "billing", "internal" ], "summary": "Create a SetupIntent for in-app card collection (Stripe Payment Element)", "operationId": "create_payment_method_setup_intent", "responses": { "200": { "description": "SetupIntent client secret", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_SetupIntentResponse" } } } }, "400": { "description": "Billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/plans": { "get": { "tags": [ "billing", "internal" ], "summary": "Get available billing plans", "operationId": "get_billing_plans", "responses": { "200": { "description": "List of available billing plans", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vec_BillingPlan" } } } }, "400": { "description": "Billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/portal": { "post": { "tags": [ "billing", "internal" ], "summary": "Create a billing portal session", "operationId": "create_portal_session", "requestBody": { "content": { "text/plain": { "schema": { "type": "string" } } }, "required": true }, "responses": { "200": { "description": "Portal session URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "Billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/reactivate": { "post": { "tags": [ "billing", "internal" ], "summary": "Reactivate a subscription pending cancellation", "description": "Clears Stripe's scheduled-cancellation state (`cancel_at` → None).\nAvailable while `plan_status === 'pending_cancellation'`.", "operationId": "reactivate_subscription", "responses": { "200": { "description": "Subscription reactivated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "No pending cancellation or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/resume": { "post": { "tags": [ "billing", "internal" ], "summary": "Resume a paused subscription", "description": "Clears Stripe pause collection and re-activates billing. Available while\n`plan_status === 'paused'`. The prorated pause credit is posted to the\ncustomer's Stripe balance asynchronously by the webhook arm that fires\nfor the `pause_collection` clear — the endpoint just returns success.", "operationId": "resume_subscription", "responses": { "200": { "description": "Subscription resumed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "400": { "description": "No paused subscription or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/save-offer-coupon": { "get": { "tags": [ "billing", "internal" ], "summary": "Read live terms for the configured save-offer coupon", "description": "Returns the coupon's `percent_off` and `duration_in_months` so the\ncancel modal's Discount panel can render the offer dynamically. The\npayload is `null` when `STRIPE_SAVE_OFFER_COUPON_ID` is unset — the\nmodal hides the panel in that case.", "operationId": "get_save_offer_coupon", "responses": { "200": { "description": "Save-offer coupon terms, or null when not configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Option_SaveOfferCoupon" } } } }, "400": { "description": "Billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/billing/webhooks": { "post": { "tags": [ "billing", "internal" ], "summary": "Handle Stripe webhook", "description": "Internal endpoint for Stripe webhook callbacks.", "operationId": "handle_webhook", "responses": { "200": { "description": "Webhook processed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Invalid signature or billing not enabled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/config": { "get": { "tags": [ "config", "internal" ], "summary": "Get public server configuration", "description": "Returns public configuration settings like OIDC providers, billing status, etc.", "operationId": "get_public_config", "responses": { "200": { "description": "Public server configuration", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_PublicConfigResponse" } } } } } } }, "/api/daemons/register": { "post": { "tags": [ "Daemons", "internal" ], "summary": "Register a new Daemon", "description": "Internal endpoint for daemon self-registration. Creates a host entry\nand sets up default discovery jobs for the daemon.", "operationId": "register_daemon", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonRegistrationRequest" } } }, "required": true }, "responses": { "200": { "description": "Daemon registered successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DaemonRegistrationResponse" } } } }, "403": { "description": "Daemon registration disabled in demo mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/daemons/{id}/heartbeat": { "post": { "tags": [ "Daemons", "internal", "deprecated" ], "summary": "Receive daemon heartbeat (DEPRECATED - for backwards compatibility with pre-v0.14.0 daemons)", "description": "Internal endpoint for legacy daemons to send periodic heartbeats.\nNew daemons (v0.14.0+) use the /request-work endpoint which includes heartbeat functionality.\nThis endpoint is kept for backwards compatibility and will be removed in a future version.", "operationId": "receive_heartbeat", "parameters": [ { "name": "id", "in": "path", "description": "Daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonHeartbeatPayload" } } }, "required": true }, "responses": { "200": { "description": "Heartbeat received", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/daemons/{id}/request-work": { "post": { "tags": [ "Daemons", "internal" ], "summary": "Request work from server", "description": "Internal endpoint for daemons to poll for pending discovery sessions.\nAlso updates heartbeat and returns any pending cancellation requests.\nReturns tuple of (next_session, should_cancel).", "operationId": "receive_work_request", "parameters": [ { "name": "id", "in": "path", "description": "Daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonStatus" } } }, "required": true }, "responses": { "200": { "description": "Work request processed - returns (Option, bool)" }, "404": { "description": "Daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/daemons/{id}/startup": { "post": { "tags": [ "Daemons", "internal" ], "summary": "Daemon startup handshake", "description": "Internal endpoint for daemons to report their version on startup.\nUpdates the daemon's version and last_seen timestamp, returns server capabilities.", "operationId": "daemon_startup", "parameters": [ { "name": "id", "in": "path", "description": "Daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonStartupRequest" } } }, "required": true }, "responses": { "200": { "description": "Startup acknowledged", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_ServerCapabilities" } } } }, "404": { "description": "Daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/daemons/{id}/update-capabilities": { "post": { "tags": [ "Daemons", "internal" ], "summary": "Update Daemon capabilities", "description": "Internal endpoint for daemons to report their current capabilities.", "operationId": "update_capabilities", "parameters": [ { "name": "id", "in": "path", "description": "Daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonCapabilities" } } }, "required": true }, "responses": { "200": { "description": "Capabilities updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/github-stars": { "get": { "tags": [ "github", "internal" ], "summary": "Get GitHub star count", "description": "Returns the current star count for the Scanopy GitHub repository.", "operationId": "get_stars", "responses": { "200": { "description": "GitHub star count", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_u32" } } } } } } }, "/api/v1/auth/daemon": { "get": { "tags": [ "Daemon API Keys" ], "summary": "List all Daemon API Keys", "operationId": "list_daemon_api_keys", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of Daemon API Keys", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/DaemonApiKey" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Daemon API Keys" ], "summary": "Create Daemon API Key", "operationId": "create_daemon_api_key", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonApiKey" } } }, "required": true }, "responses": { "200": { "description": "Daemon API key created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DaemonApiKeyResponse" } } } }, "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Insufficient permissions (member+ required)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/auth/daemon/bulk-delete": { "post": { "tags": [ "Daemon API Keys" ], "summary": "Bulk delete daemon_api_keys", "description": "Returns 409 Conflict if any key is currently assigned to a daemon.", "operationId": "bulk_delete_daemon_api_keys", "requestBody": { "description": "Array of daemon_api_key IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "daemon_api_keys deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "409": { "description": "One or more API keys are in use by daemons", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/auth/daemon/export/csv": { "get": { "tags": [ "Daemon API Keys" ], "summary": "Export Daemon API Keys to CSV", "description": "Export all Daemon API Keys matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_daemon_api_keys_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Daemon API Keys", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/auth/daemon/{id}": { "get": { "tags": [ "Daemon API Keys" ], "summary": "Get Daemon API Key by ID", "operationId": "get_daemon_api_key_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Daemon API Key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Daemon API Key found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DaemonApiKey" } } } }, "404": { "description": "Daemon API Key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Daemon API Keys" ], "summary": "Update a Daemon API Key", "operationId": "update_daemon_api_key", "parameters": [ { "name": "id", "in": "path", "description": "Daemon API key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonApiKey" } } }, "required": true }, "responses": { "200": { "description": "Daemon API key updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DaemonApiKey" } } } }, "404": { "description": "Daemon API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Daemon API Keys" ], "summary": "Delete daemon_api_key", "description": "Returns 409 Conflict if the key is currently assigned to a daemon.", "operationId": "delete_daemon_api_key", "parameters": [ { "name": "id", "in": "path", "description": "daemon_api_key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "daemon_api_key deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "daemon_api_key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "API key is in use by a daemon", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/auth/daemon/{id}/rotate": { "post": { "tags": [ "Daemon API Keys" ], "summary": "Rotate a Daemon API Key", "operationId": "rotate_key_handler", "parameters": [ { "name": "id", "in": "path", "description": "Daemon API key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Daemon API key rotated, returns new key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "404": { "description": "Daemon API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/auth/keys": { "get": { "tags": [ "User API Keys" ], "summary": "Get all user API keys for the current user", "operationId": "get_all_user_api_keys", "parameters": [ { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of user API keys", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_UserApiKey" } } } }, "401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] }, "post": { "tags": [ "User API Keys" ], "summary": "Create a new user API key", "operationId": "create_user_api_key", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserApiKey" } } }, "required": true }, "responses": { "200": { "description": "API key created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_UserApiKeyResponse" } } } }, "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Invalid permissions or network access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] } }, "/api/v1/auth/keys/bulk-delete": { "post": { "tags": [ "User API Keys" ], "summary": "Bulk delete user API keys", "operationId": "bulk_delete_user_api_keys", "requestBody": { "description": "Array of API key IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "API keys deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "session": [] } ] } }, "/api/v1/auth/keys/export/csv": { "get": { "tags": [ "User API Keys" ], "summary": "Export User API Keys to CSV", "description": "Export all User API Keys matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_user_api_keys_csv", "parameters": [ { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing User API Keys", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/auth/keys/{id}": { "get": { "tags": [ "User API Keys" ], "summary": "Get a user API key by ID", "operationId": "get_user_api_key_by_id", "parameters": [ { "name": "id", "in": "path", "description": "API key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "API key found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_UserApiKey" } } } }, "404": { "description": "API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] }, "put": { "tags": [ "User API Keys" ], "summary": "Update a user API key", "operationId": "update_user_api_key", "parameters": [ { "name": "id", "in": "path", "description": "API key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserApiKey" } } }, "required": true }, "responses": { "200": { "description": "API key updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_UserApiKey" } } } }, "403": { "description": "Not authorized to update this key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] }, "delete": { "tags": [ "User API Keys" ], "summary": "Delete a user API key", "operationId": "delete_user_api_key", "parameters": [ { "name": "id", "in": "path", "description": "API key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "API key deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] } }, "/api/v1/auth/keys/{id}/rotate": { "post": { "tags": [ "User API Keys" ], "summary": "Rotate a user API key", "operationId": "rotate_user_api_key", "parameters": [ { "name": "id", "in": "path", "description": "API key ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "API key rotated, returns new key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_String" } } } }, "403": { "description": "Not authorized to rotate this key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] } }, "/api/v1/bindings": { "get": { "tags": [ "Bindings" ], "summary": "List all Bindings", "operationId": "list_bindings", "parameters": [ { "name": "service_id", "in": "query", "description": "Filter by service ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "port_id", "in": "query", "description": "Filter by port ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ip_address_id", "in": "query", "description": "Filter by interface ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of Bindings", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Binding" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Bindings" ], "summary": "Create a new Binding", "description": "Creates a binding that associates a service with a port or interface.\n\n### Binding Types\n\n- **Interface binding**: Service is present at an interface (IP address) without a specific port.\n Used for non-port-bound services like gateways.\n- **Port binding (specific ip_address)**: Service listens on a specific port on a specific interface.\n- **Port binding (all ip_addresses)**: Service listens on a specific port on all ip_addresses\n (`ip_address_id: null`).\n\n### Validation and Deduplication Rules\n\n- **Conflict detection**: Interface bindings conflict with port bindings on the same interface.\n A port binding on all ip_addresses conflicts with any interface binding for the same service.\n- **All-interfaces precedence**: When creating a port binding with `ip_address_id: null`,\n any existing specific-interface bindings for the same port are automatically removed,\n as they are superseded by the all-interfaces binding.", "operationId": "create_binding", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Binding" } } }, "required": true }, "responses": { "200": { "description": "Binding created (superseded bindings may be removed)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Binding" } } } }, "400": { "description": "Referenced port or ip_address does not exist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Conflict with existing binding type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/bindings/bulk-delete": { "post": { "tags": [ "Bindings" ], "summary": "Bulk delete Bindings", "operationId": "bulk_delete_bindings", "requestBody": { "description": "Array of Binding IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Bindings deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/bindings/export/csv": { "get": { "tags": [ "Bindings" ], "summary": "Export Bindings to CSV", "description": "Export all Bindings matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_bindings_csv", "parameters": [ { "name": "service_id", "in": "query", "description": "Filter by service ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "port_id", "in": "query", "description": "Filter by port ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ip_address_id", "in": "query", "description": "Filter by interface ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Bindings", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/bindings/{id}": { "get": { "tags": [ "Bindings" ], "summary": "Get Binding by ID", "operationId": "get_binding_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Binding ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Binding found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Binding" } } } }, "404": { "description": "Binding not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Bindings" ], "summary": "Update a Binding", "description": "Updates an existing binding. The same conflict detection rules from binding creation apply.\n\n## Validation Rules\n\n- **Conflict detection**: The updated binding must not conflict with other bindings on the\n same service. Interface bindings conflict with port bindings on the same interface.", "operationId": "update_binding", "parameters": [ { "name": "id", "in": "path", "description": "Binding ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Binding" } } }, "required": true }, "responses": { "200": { "description": "Binding updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Binding" } } } }, "400": { "description": "Referenced port or ip_address does not exist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Conflict with existing binding type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Bindings" ], "summary": "Delete Binding", "operationId": "delete_binding", "parameters": [ { "name": "id", "in": "path", "description": "Binding ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Binding deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Binding not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/credentials": { "get": { "tags": [ "Credentials" ], "summary": "List all Credentials", "description": "Returns all credentials in the authenticated user's organization.\nOptionally filter by type (e.g. ?type=Snmp).", "operationId": "get_all_credentials", "parameters": [ { "name": "type", "in": "query", "description": "Filter by credential type (e.g. \"Snmp\", \"DockerProxy\")", "required": false, "schema": { "type": [ "string", "null" ] } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/CredentialOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/CredentialOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Credential" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Credentials" ], "summary": "Create a new Credential", "description": "Creates a credential scoped to your organization.", "operationId": "create_credential", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Credential" } } }, "required": true }, "responses": { "200": { "description": "Credential created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Credential" } } } }, "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/credentials/bulk": { "post": { "tags": [ "Credentials" ], "summary": "Bulk create Credentials", "description": "Creates multiple credentials in one request. Validation is atomic — if any\ncredential has an invalid type, none are created. Individual creates are\nsequential, so a mid-batch DB error leaves earlier credentials committed.", "operationId": "bulk_create_credentials", "requestBody": { "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Credential" } } } }, "required": true }, "responses": { "200": { "description": "Credentials created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vec_Credential" } } } }, "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/credentials/bulk-delete": { "post": { "tags": [ "Credentials" ], "summary": "Bulk delete Credentials", "operationId": "bulk_delete_credentials", "requestBody": { "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Credentials deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/credentials/export/csv": { "get": { "tags": [ "Credentials" ], "summary": "Export Credentials to CSV", "description": "Export all Credentials matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_credentials_csv", "parameters": [ { "name": "type", "in": "query", "description": "Filter by credential type (e.g. \"Snmp\", \"DockerProxy\")", "required": false, "schema": { "type": [ "string", "null" ] } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/CredentialOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/CredentialOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Credentials", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/credentials/{id}": { "get": { "tags": [ "Credentials" ], "summary": "Get a Credential by ID", "operationId": "get_by_id_credential", "parameters": [ { "name": "id", "in": "path", "description": "Credential ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Credential found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Credential" } } } }, "404": { "description": "Credential not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Credentials" ], "summary": "Update Credential", "operationId": "update_credential", "parameters": [ { "name": "id", "in": "path", "description": "Credential ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Credential" } } }, "required": true }, "responses": { "200": { "description": "Credential updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Credential" } } } }, "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Credential not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Credentials" ], "summary": "Delete Credential", "operationId": "delete_credential", "parameters": [ { "name": "id", "in": "path", "description": "Credential ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Credential deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Credential not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons": { "get": { "tags": [ "Daemons" ], "summary": "Get all daemons", "description": "Returns all daemons accessible to the user.\nSupports pagination via `limit` and `offset` query parameters,\nand ordering via `group_by`, `order_by`, and `order_direction`.", "operationId": "get_daemons", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DaemonOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DaemonOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of daemons", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_DaemonResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/bulk-delete": { "post": { "tags": [ "daemons" ], "summary": "Bulk delete daemons", "operationId": "bulk_delete_daemons", "requestBody": { "description": "Array of daemon IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "daemons deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "409": { "description": "daemon has active sessions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/email-install-command": { "post": { "tags": [ "daemons" ], "summary": "Email install command to current user", "operationId": "email_install_command", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailInstallCommandRequest" } } }, "required": true }, "responses": { "200": { "description": "Email sent", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Email service not configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/export/csv": { "get": { "tags": [ "Daemons" ], "summary": "Export Daemons to CSV", "description": "Export all Daemons matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_daemons_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DaemonOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DaemonOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Daemons", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/provision": { "post": { "tags": [ "internal", "Daemons" ], "summary": "Pre-provision a ServerPoll mode daemon", "description": "Creates a daemon record on the server before the daemon is installed.\nThis is only for ServerPoll mode where the server initiates connections to the daemon.\nFor DaemonPoll mode, daemons self-register on startup.\n\nReturns the daemon record and an API key that must be configured on the daemon.", "operationId": "provision_daemon", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProvisionDaemonRequest" } } }, "required": true }, "responses": { "201": { "description": "Daemon provisioned successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_ProvisionDaemonResponse" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/test-reachability": { "post": { "tags": [ "Daemons" ], "summary": "Test reachability of a daemon URL", "description": "Performs a TCP connection test and optionally an HTTP health check\nto verify that a daemon URL is reachable from the server.", "operationId": "test_daemon_reachability", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TestReachabilityRequest" } } }, "required": true }, "responses": { "200": { "description": "Reachability test result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_TestReachabilityResponse" } } } }, "400": { "description": "Invalid URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/{id}": { "get": { "tags": [ "Daemons" ], "summary": "Get daemon by ID", "description": "Returns a specific daemon with computed version status.", "operationId": "get_daemon_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Daemon found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DaemonResponse" } } } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "daemons" ], "summary": "Delete daemon", "operationId": "delete_daemon", "parameters": [ { "name": "id", "in": "path", "description": "daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "daemon deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "daemon has active sessions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/daemons/{id}/retry-connection": { "post": { "tags": [ "Daemons" ], "summary": "Retry connection to unreachable daemon", "description": "Resets the is_unreachable flag for a daemon that was marked unreachable\ndue to repeated polling failures. The poller will attempt to contact\nthe daemon again on the next cycle.", "operationId": "retry_daemon_connection", "parameters": [ { "name": "id", "in": "path", "description": "Daemon ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Connection retry initiated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Daemon not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/dashboard/summary": { "get": { "tags": [ "dashboard", "internal" ], "summary": "Get dashboard summary", "description": "Returns aggregated dashboard data including network metrics, daemon health,\nrecent discoveries, and plan usage.", "operationId": "get_dashboard_summary", "responses": { "200": { "description": "Dashboard summary", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DashboardSummary" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/dependencies": { "get": { "tags": [ "Dependencies" ], "summary": "List all Dependencies", "description": "Returns all dependencies the authenticated user has access to.\nSupports pagination via `limit` and `offset` query parameters,\nand ordering via `group_by`, `order_by`, and `order_direction`.", "operationId": "get_all_dependencies", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DependencyOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DependencyOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of dependencies", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Dependency" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Dependencies" ], "summary": "Create a new Dependency", "operationId": "create_dependency", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Dependency" } } }, "required": true }, "responses": { "200": { "description": "Dependency created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Dependency" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/dependencies/bulk-delete": { "post": { "tags": [ "Dependencies" ], "summary": "Bulk delete Dependencies", "operationId": "bulk_delete_dependencies", "requestBody": { "description": "Array of Dependency IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Dependencies deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/dependencies/export/csv": { "get": { "tags": [ "Dependencies" ], "summary": "Export Dependencies to CSV", "description": "Export all Dependencies matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_dependencies_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DependencyOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DependencyOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Dependencies", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/dependencies/{id}": { "get": { "tags": [ "Dependencies" ], "summary": "Get Dependency by ID", "operationId": "get_dependency_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Dependency ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Dependency found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Dependency" } } } }, "404": { "description": "Dependency not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Dependencies" ], "summary": "Update a Dependency", "operationId": "update_dependency", "parameters": [ { "name": "id", "in": "path", "description": "Dependency ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Dependency" } } }, "required": true }, "responses": { "200": { "description": "Dependency updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Dependency" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Dependency not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Dependencies" ], "summary": "Delete Dependency", "operationId": "delete_dependency", "parameters": [ { "name": "id", "in": "path", "description": "Dependency ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Dependency deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Dependency not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery": { "get": { "tags": [ "Discoveries" ], "summary": "List all Discoveries", "operationId": "list_discoveries", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "daemon_id", "in": "query", "description": "Filter by daemon ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of Discoveries", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Discovery" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Discoveries" ], "summary": "Create new Discovery", "operationId": "create_discovery", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Discovery" } } }, "required": true }, "responses": { "200": { "description": "Discovery created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Discovery" } } } }, "400": { "description": "Can't create historical discovery", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/active-sessions": { "get": { "tags": [ "Discoveries" ], "summary": "Get active Discovery Sessions", "operationId": "get_active_sessions", "responses": { "200": { "description": "List of active discovery sessions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vec_DiscoveryUpdatePayload" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/bulk-delete": { "post": { "tags": [ "Discoveries" ], "summary": "Bulk delete discoveries", "operationId": "bulk_delete_discoveries", "requestBody": { "description": "Array of discovery IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "discoveries deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "409": { "description": "discovery has active session", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/export/csv": { "get": { "tags": [ "Discoveries" ], "summary": "Export Discoveries to CSV", "description": "Export all Discoveries matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_discoveries_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "daemon_id", "in": "query", "description": "Filter by daemon ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Discoveries", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/start-session": { "post": { "tags": [ "Discoveries" ], "summary": "Start a Discovery Session", "operationId": "start_session", "requestBody": { "content": { "text/plain": { "schema": { "type": "string", "format": "uuid" } } }, "required": true }, "responses": { "200": { "description": "Discovery session started", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_DiscoveryUpdatePayload" } } } }, "404": { "description": "Discovery not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "A session is already running for this discovery", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/{id}": { "get": { "tags": [ "Discoveries" ], "summary": "Get Discovery by ID", "operationId": "get_discovery_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Discovery ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Discovery found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Discovery" } } } }, "404": { "description": "Discovery not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Discoveries" ], "summary": "Update Discovery", "operationId": "update_discovery", "parameters": [ { "name": "id", "in": "path", "description": "Discovery ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Discovery" } } }, "required": true }, "responses": { "200": { "description": "Discovery updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Discovery" } } } }, "400": { "description": "Can't update historical discovery", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Discoveries" ], "summary": "Delete discovery", "operationId": "delete_discovery", "parameters": [ { "name": "id", "in": "path", "description": "discovery ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "discovery deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "discovery not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "discovery has active session", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/{session_id}/cancel": { "post": { "tags": [ "Discoveries" ], "summary": "Cancel a Discovery Session", "operationId": "cancel_discovery", "parameters": [ { "name": "session_id", "in": "path", "description": "Session ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Discovery session cancelled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/discovery/{session_id}/update": { "post": { "tags": [ "Discoveries", "internal" ], "summary": "Receive discovery progress update from daemon", "description": "Internal endpoint for daemons to report discovery progress.", "operationId": "receive_discovery_update", "parameters": [ { "name": "session_id", "in": "path", "description": "Discovery session ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DiscoveryUpdatePayload" } } }, "required": true }, "responses": { "200": { "description": "Update received", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/v1/hosts": { "get": { "tags": [ "Hosts" ], "summary": "List all hosts", "description": "Returns all hosts the authenticated user has access to, with their\nip_addresses, ports, and services included. Supports pagination via\n`limit` and `offset` query parameters, and ordering via `group_by`,\n`order_by`, and `order_direction`.", "operationId": "get_all_hosts", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "tag_ids", "in": "query", "description": "Filter by tag IDs (returns hosts that have ANY of the specified tags)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of hosts with their children", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_HostResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Hosts" ], "summary": "Create a new host", "description": "Creates a host with optional ip_addresses, ports, and services.\nThe `source` field is automatically set to `Manual`.\n\n### Tag Validation\n\n- Tags must exist and belong to your organization\n- Duplicate tag UUIDs are automatically deduplicated\n- Invalid or cross-organization tag UUIDs return a 400 error\n\n", "operationId": "create_host", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateHostRequest" } } }, "required": true }, "responses": { "200": { "description": "Host created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_HostResponse" } } } }, "400": { "description": "Validation error: network not found, subnet mismatch, or invalid tags", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "401": { "description": "No access to the specified network", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] }, { "daemon_api_key": [] } ] } }, "/api/v1/hosts/bulk-delete": { "post": { "tags": [ "Hosts" ], "summary": "Bulk delete hosts", "description": "Deletes multiple hosts in a single request. The request body should be\nan array of host IDs to delete. Fails if any host has an associated daemon.", "operationId": "bulk_delete_hosts", "requestBody": { "description": "Array of host IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Hosts deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "409": { "description": "One or more hosts has an associated daemon - delete daemons first", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/hosts/discovery": { "post": { "tags": [ "hosts", "internal" ], "summary": "Internal endpoint for daemon discovery", "description": "Used by daemons to report discovered hosts. Accepts full entities with\npre-generated IDs. Uses upsert behavior to merge with existing hosts.\n\nTagged as \"internal\" - included in OpenAPI spec for client generation\nbut hidden from public documentation.", "operationId": "create_host_discovery", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DiscoveryHostRequest" } } }, "required": true }, "responses": { "200": { "description": "Host discovered/updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_HostResponse" } } } }, "403": { "description": "Daemon cannot create hosts on other networks", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/v1/hosts/export/csv": { "get": { "tags": [ "Hosts" ], "summary": "Export Hosts to CSV", "description": "Export all Hosts matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_hosts_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "tag_ids", "in": "query", "description": "Filter by tag IDs (returns hosts that have ANY of the specified tags)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Hosts", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/hosts/export/zip": { "get": { "tags": [ "Hosts" ], "summary": "Export hosts with children to ZIP", "description": "Exports all hosts matching the filter criteria along with their children\n(ip_addresses, ports, services, interfaces) as a ZIP archive containing\nseparate CSV files for each entity type.", "operationId": "export_hosts_zip", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "tag_ids", "in": "query", "description": "Filter by tag IDs (returns hosts that have ANY of the specified tags)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "ZIP file containing CSVs", "content": { "application/zip": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/hosts/{destination_host}/consolidate/{other_host}": { "put": { "tags": [ "Hosts" ], "summary": "Consolidate hosts", "description": "Merges all ip_addresses, ports, and services from `other_host` into\n`destination_host`, then deletes `other_host`. Both hosts must be\non the same network.\n\n### Merge Behavior\n\n- **Interfaces**: Transferred to destination. If an interface with matching subnet+IP or MAC\n already exists on destination, bindings are remapped to use the existing interface.\n- **Ports**: Transferred to destination. If a port with the same number and protocol already\n exists, bindings are remapped to use the existing port.\n- **Services**: Transferred to destination with deduplication.\n See [upsert behavior](https://scanopy.net/docs/discovery/#upsert-behavior) for details.\n\n### Restrictions\n\n- Cannot consolidate a host with itself.\n- Cannot consolidate a host that has a daemon - consolidate into it instead.", "operationId": "consolidate_hosts", "parameters": [ { "name": "destination_host", "in": "path", "description": "Destination host ID - will receive all children", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "other_host", "in": "path", "description": "Host to merge into destination - will be deleted", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Hosts consolidated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_HostResponse" } } } }, "400": { "description": "Validation error: same host, has daemon, or different networks", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "One or both hosts not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/hosts/{id}": { "get": { "tags": [ "Hosts" ], "summary": "Get a host by ID", "description": "Returns a single host with its ip_addresses, ports, and services.", "operationId": "get_host_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Host ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Host found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_HostResponse" } } } }, "404": { "description": "Host not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Hosts" ], "summary": "Update a host", "description": "Updates host properties. Children (ip_addresses, ports, services)\nare managed via their own endpoints.\n\n### Tag Validation\n\n- Tags must exist and belong to your organization\n- Duplicate tag UUIDs are automatically deduplicated\n- Invalid or cross-organization tag UUIDs return a 400 error", "operationId": "update_host", "parameters": [ { "name": "id", "in": "path", "description": "Host ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateHostRequest" } } }, "required": true }, "responses": { "200": { "description": "Host updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_HostResponse" } } } }, "400": { "description": "Validation error: invalid tags", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Host not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Hosts" ], "summary": "Delete a host", "description": "Prevents deletion if the host has a daemon associated with it", "operationId": "delete_host", "parameters": [ { "name": "id", "in": "path", "description": "Host ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Host deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Host not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Host has associated daemon", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/if-entries": { "get": { "tags": [ "Interfaces" ], "summary": "List all Interfaces", "operationId": "list_interfaces", "parameters": [ { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of Interfaces", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Interfaces" ], "summary": "Create a new Interface", "description": "Creates an SNMP ifTable entry for a host. These are typically created by\nSNMP discovery, but can also be created manually.", "operationId": "create_if_entry", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Interface" } } }, "required": true }, "responses": { "200": { "description": "If entry created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Interface" } } } }, "400": { "description": "Network mismatch or duplicate if_index", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/if-entries/bulk-delete": { "post": { "tags": [ "Interfaces" ], "summary": "Bulk delete Interfaces", "operationId": "bulk_delete_interfaces", "requestBody": { "description": "Array of Interface IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Interfaces deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/if-entries/export/csv": { "get": { "tags": [ "Interfaces" ], "summary": "Export Interfaces to CSV", "description": "Export all Interfaces matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_interfaces_csv", "parameters": [ { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Interfaces", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/if-entries/{id}": { "get": { "tags": [ "Interfaces" ], "summary": "Get Interface by ID", "operationId": "get_interface_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Interface ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Interface found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Interface" } } } }, "404": { "description": "Interface not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Interfaces" ], "summary": "Update an Interface", "operationId": "update_if_entry", "parameters": [ { "name": "id", "in": "path", "description": "If entry ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Interface" } } }, "required": true }, "responses": { "200": { "description": "If entry updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Interface" } } } }, "400": { "description": "Network mismatch or invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "If entry not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Interfaces" ], "summary": "Delete Interface", "operationId": "delete_interface", "parameters": [ { "name": "id", "in": "path", "description": "Interface ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Interface deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Interface not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/invites": { "get": { "tags": [ "Invites" ], "summary": "List all invites", "operationId": "get_invites", "responses": { "200": { "description": "List of active invites", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vec_Invite" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Invites" ], "summary": "Create invite", "operationId": "create_invite", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateInviteRequest" } } }, "required": true }, "responses": { "200": { "description": "Invite created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Invite" } } } }, "403": { "description": "Cannot create invite with higher permissions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/invites/{id}": { "get": { "tags": [ "Invites" ], "summary": "Get an invite by ID", "operationId": "get_invite", "parameters": [ { "name": "id", "in": "path", "description": "Invite ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Invite details", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Invite" } } } }, "400": { "description": "Invalid or expired invite", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/invites/{id}/revoke": { "delete": { "tags": [ "Invites" ], "summary": "Revoke an invite", "operationId": "revoke_invite", "parameters": [ { "name": "id", "in": "path", "description": "Invite ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Invite revoked", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Invalid invite", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "403": { "description": "Cannot revoke this invite", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ip-addresses": { "get": { "tags": [ "IP Addresses" ], "summary": "List all IP Addresses", "operationId": "list_ip_addresses", "parameters": [ { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "subnet_id", "in": "query", "description": "Filter by subnet ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of IP Addresses", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "IP Addresses" ], "summary": "Create a new IP address\nPosition is automatically assigned to the end of the host's IP address list.", "operationId": "create_ip_address", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IPAddress" } } }, "required": true }, "responses": { "200": { "description": "IP address created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_IPAddress" } } } }, "400": { "description": "Network mismatch or invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ip-addresses/bulk-delete": { "post": { "tags": [ "IP Addresses" ], "summary": "Bulk delete IP addresses\nRemaining IP addresses for affected hosts are renumbered to maintain sequential positions.", "operationId": "bulk_delete_ip_addresses", "requestBody": { "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "IP addresses deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "400": { "description": "No IDs provided", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ip-addresses/export/csv": { "get": { "tags": [ "IP Addresses" ], "summary": "Export IP Addresses to CSV", "description": "Export all IP Addresses matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_ip_addresses_csv", "parameters": [ { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "subnet_id", "in": "query", "description": "Filter by subnet ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing IP Addresses", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ip-addresses/{id}": { "get": { "tags": [ "IP Addresses" ], "summary": "Get IP Address by ID", "operationId": "get_ip_address_by_id", "parameters": [ { "name": "id", "in": "path", "description": "IP Address ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "IP Address found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_IPAddress" } } } }, "404": { "description": "IP Address not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "IP Addresses" ], "summary": "Update an IP address\nPosition must be within valid range and not conflict with other IP addresses.", "operationId": "update_ip_address", "parameters": [ { "name": "id", "in": "path", "description": "IP address ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IPAddress" } } }, "required": true }, "responses": { "200": { "description": "IP address updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_IPAddress" } } } }, "400": { "description": "Network mismatch or invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "IP address not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "IP Addresses" ], "summary": "Delete an IP address\nRemaining IP addresses for the host are renumbered to maintain sequential positions.", "operationId": "delete_ip_address", "parameters": [ { "name": "id", "in": "path", "description": "IP address ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "IP address deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "IP address not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/networks": { "get": { "tags": [ "Networks" ], "summary": "List all networks", "operationId": "get_all_networks", "parameters": [ { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of networks", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/NetworkBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ], "example": { "created_at": "2026-01-15T10:30:00Z", "credential_ids": [], "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Home Network", "organization_id": "550e8400-e29b-41d4-a716-446655440001", "tags": [], "updated_at": "2026-01-15T10:30:00Z" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Networks" ], "summary": "Create a new network", "operationId": "create_network", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Network" } } }, "required": true }, "responses": { "200": { "description": "Network created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Network" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/networks/bulk-delete": { "post": { "tags": [ "Networks" ], "summary": "Bulk delete networks", "operationId": "bulk_delete_networks", "requestBody": { "description": "Array of network IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Networks deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "403": { "description": "User not admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/networks/export/csv": { "get": { "tags": [ "Networks" ], "summary": "Export Networks to CSV", "description": "Export all Networks matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_networks_csv", "parameters": [ { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Networks", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/networks/{id}": { "get": { "tags": [ "Networks" ], "summary": "Get a network by ID", "operationId": "get_by_id_network", "parameters": [ { "name": "id", "in": "path", "description": "Network ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Network found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Network" } } } }, "404": { "description": "Network not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Networks" ], "summary": "Update a network", "operationId": "update_network", "parameters": [ { "name": "id", "in": "path", "description": "Network ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Network" } } }, "required": true }, "responses": { "200": { "description": "Network updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Network" } } } }, "403": { "description": "User not admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Network not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Networks" ], "summary": "Delete a network", "operationId": "delete_network", "parameters": [ { "name": "id", "in": "path", "description": "Network ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Network deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "403": { "description": "User not admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Network not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/organizations": { "get": { "tags": [ "Organizations" ], "summary": "Get the current user's organization", "operationId": "get_organization", "responses": { "200": { "description": "Organization details", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Organization" } } } }, "404": { "description": "Organization not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] } }, "/api/v1/organizations/daemon-prompt-response": { "post": { "tags": [ "Organizations" ], "summary": "Record the user's response to the daemon-install prompt so it is not shown again.\nEach CTA persists a distinct onboarding milestone (the org subscriber dedups); the\nPostHog subscriber turns these into funnel events, so no client-side telemetry is needed.", "operationId": "daemon_prompt_response", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DaemonPromptResponseRequest" } } }, "required": true }, "responses": { "200": { "description": "Response recorded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } } } }, "/api/v1/organizations/profile": { "post": { "tags": [ "Organizations" ], "summary": "Update user profile with deferred marketing fields", "operationId": "update_profile", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfileUpdateRequest" } } }, "required": true }, "responses": { "200": { "description": "Profile updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } } } }, "/api/v1/organizations/referral-source": { "post": { "tags": [ "Organizations" ], "summary": "Submit referral source (how did you hear about us)", "operationId": "submit_referral_source", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReferralSourceRequest" } } }, "required": true }, "responses": { "200": { "description": "Referral source recorded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } } } } }, "/api/v1/organizations/{id}": { "put": { "tags": [ "Organizations" ], "summary": "Update organization name", "operationId": "update_org_name", "parameters": [ { "name": "id", "in": "path", "description": "Organization ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "text/plain": { "schema": { "type": "string" } } }, "required": true }, "responses": { "200": { "description": "Organization updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Organization" } } } }, "403": { "description": "Only owners can update organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Organization not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Organizations", "internal" ], "summary": "Delete the organization entirely, including all data and users", "operationId": "delete_organization", "parameters": [ { "name": "id", "in": "path", "description": "Organization ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Organization deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "403": { "description": "Cannot delete another organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Organization not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] } }, "/api/v1/organizations/{id}/populate-demo": { "post": { "tags": [ "Organizations", "internal" ], "summary": "Populate demo data (only available for demo organizations)", "operationId": "populate_demo_data", "parameters": [ { "name": "id", "in": "path", "description": "Organization ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Demo data populated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "403": { "description": "Only available for demo organizations", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Organization not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/organizations/{id}/reset": { "post": { "tags": [ "Organizations", "internal" ], "summary": "Reset all organization data (delete all entities except organization and owner user)", "operationId": "reset", "parameters": [ { "name": "id", "in": "path", "description": "Organization ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Organization reset", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "403": { "description": "Cannot reset another organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Organization not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ports": { "get": { "tags": [ "Ports" ], "summary": "List all Ports", "operationId": "list_ports", "parameters": [ { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of Ports", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Ports" ], "summary": "Create a new port", "operationId": "create_port", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Port" } } }, "required": true }, "responses": { "200": { "description": "Port created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Port" } } } }, "400": { "description": "Network mismatch or duplicate port", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ports/bulk-delete": { "post": { "tags": [ "Ports" ], "summary": "Bulk delete Ports", "operationId": "bulk_delete_ports", "requestBody": { "description": "Array of Port IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Ports deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ports/export/csv": { "get": { "tags": [ "Ports" ], "summary": "Export Ports to CSV", "description": "Export all Ports matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_ports_csv", "parameters": [ { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Ports", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/ports/{id}": { "get": { "tags": [ "Ports" ], "summary": "Get Port by ID", "operationId": "get_port_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Port ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Port found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Port" } } } }, "404": { "description": "Port not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Ports" ], "summary": "Update a port", "operationId": "update_port", "parameters": [ { "name": "id", "in": "path", "description": "Port ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Port" } } }, "required": true }, "responses": { "200": { "description": "Port updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Port" } } } }, "400": { "description": "Network mismatch or invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Port not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Ports" ], "summary": "Delete Port", "operationId": "delete_port", "parameters": [ { "name": "id", "in": "path", "description": "Port ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Port deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Port not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/services": { "get": { "tags": [ "Services" ], "summary": "List all services", "description": "Returns all services the authenticated user has access to.\nSupports pagination via `limit` and `offset` query parameters,\nand ordering via `group_by`, `order_by`, and `order_direction`.", "operationId": "get_all_services", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "tag_ids", "in": "query", "description": "Filter by tag IDs (returns services that have ANY of the specified tags)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "exclude_categories", "in": "query", "description": "Exclude services belonging to these categories.", "required": false, "schema": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/ServiceCategory" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of services", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Service" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Services" ], "summary": "Create a new service", "description": "Creates a service with optional bindings to ip_addresses or ports.\nThe `id`, `created_at`, `updated_at`, and `source` fields are generated server-side.\nBindings are specified without `service_id` or `network_id` - these are assigned automatically.\n\n### Binding Validation Rules\n\n- **Cross-host validation**: All bindings must reference ports/interfaces that belong to the\n service's host. Bindings referencing entities from other hosts will be rejected.\n- **Deduplication**: Duplicate bindings in the same request are automatically deduplicated.\n- **All-interfaces precedence**: If a port binding with `ip_address_id: null` (all ip_addresses)\n is included, any specific-interface bindings for the same port are automatically removed.\n- **Conflict detection**: Interface bindings conflict with port bindings on the same interface.\n A port binding on all ip_addresses conflicts with any interface binding.", "operationId": "create_service", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateServiceRequest" } } }, "required": true }, "responses": { "200": { "description": "Service created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Service" } } } }, "400": { "description": "Validation error: host network mismatch, cross-host binding, or binding conflict", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/services/bulk-delete": { "post": { "tags": [ "Services" ], "summary": "Bulk delete Services", "operationId": "bulk_delete_services", "requestBody": { "description": "Array of Service IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Services deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/services/export/csv": { "get": { "tags": [ "Services" ], "summary": "Export Services to CSV", "description": "Export all Services matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_services_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "host_id", "in": "query", "description": "Filter by host ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "tag_ids", "in": "query", "description": "Filter by tag IDs (returns services that have ANY of the specified tags)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "exclude_categories", "in": "query", "description": "Exclude services belonging to these categories.", "required": false, "schema": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/ServiceCategory" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Services", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/services/{id}": { "get": { "tags": [ "Services" ], "summary": "Get Service by ID", "operationId": "get_service_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Service ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Service found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Service" } } } }, "404": { "description": "Service not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Services" ], "summary": "Update a service", "description": "Updates an existing service. All binding validation rules from service creation apply here as well.\n\n## Binding Validation Rules\n\n- **Cross-host validation**: All bindings must reference ports/interfaces that belong to the\n service's host. Bindings referencing entities from other hosts will be rejected.\n- **Deduplication**: Duplicate bindings are automatically deduplicated.\n- **All-interfaces precedence**: If a port binding with `ip_address_id: null` (all ip_addresses)\n is included, any specific-interface bindings for the same port are automatically removed.\n- **Conflict detection**: Interface bindings conflict with port bindings on the same interface.", "operationId": "update_service", "parameters": [ { "name": "id", "in": "path", "description": "Service ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Service" } } }, "required": true }, "responses": { "200": { "description": "Service updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Service" } } } }, "400": { "description": "Validation error: host network mismatch, cross-host binding, or binding conflict", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Service not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Services" ], "summary": "Delete Service", "operationId": "delete_service", "parameters": [ { "name": "id", "in": "path", "description": "Service ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Service deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Service not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/shares": { "get": { "tags": [ "Shares" ], "summary": "List all Shares", "operationId": "list_shares", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "topology_id", "in": "query", "description": "Filter by topology ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of Shares", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Share" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Shares" ], "summary": "Create a new share", "operationId": "create_share", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUpdateShareRequest" } } }, "required": true }, "responses": { "200": { "description": "Share created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Share" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/shares/bulk-delete": { "post": { "tags": [ "Shares" ], "summary": "Bulk delete Shares", "operationId": "bulk_delete_shares", "requestBody": { "description": "Array of Share IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Shares deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/shares/export/csv": { "get": { "tags": [ "Shares" ], "summary": "Export Shares to CSV", "description": "Export all Shares matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_shares_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "topology_id", "in": "query", "description": "Filter by topology ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Shares", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/shares/public/{id}": { "get": { "tags": [ "Shares" ], "summary": "Get share metadata", "description": "Does not include any topology data", "operationId": "get_public_share_metadata", "parameters": [ { "name": "id", "in": "path", "description": "Share ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Share metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_PublicShareMetadata" } } } }, "404": { "description": "Share not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/v1/shares/public/{id}/verify": { "post": { "tags": [ "Shares", "internal" ], "summary": "Verify password for a password-protected share and return an access token.", "description": "The returned token is an HS256 JWT tied to the share's current password\nhash; subsequent `/topology` calls send the token instead of the raw\npassword. Changing the share password invalidates outstanding tokens.", "operationId": "verify_share_password", "parameters": [ { "name": "id", "in": "path", "description": "Share ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "text/plain": { "schema": { "type": "string" } } }, "required": true }, "responses": { "200": { "description": "Password verified; access token issued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_ShareAccessTokenResponse" } } } }, "401": { "description": "Invalid password", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Share not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } } } }, "/api/v1/shares/{id}": { "get": { "tags": [ "Shares" ], "summary": "Get Share by ID", "operationId": "get_share_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Share ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Share found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Share" } } } }, "404": { "description": "Share not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Shares" ], "summary": "Update a share", "operationId": "update_share", "parameters": [ { "name": "id", "in": "path", "description": "Share ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUpdateShareRequest" } } }, "required": true }, "responses": { "200": { "description": "Share updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Share" } } } }, "404": { "description": "Share not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Shares" ], "summary": "Delete Share", "operationId": "delete_share", "parameters": [ { "name": "id", "in": "path", "description": "Share ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Share deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Share not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/snapshots": { "get": { "tags": [ "Snapshots" ], "summary": "List snapshots for a network, sorted by `taken_at` DESC.", "operationId": "get_all_snapshots", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of snapshots", "content": { "application/json": { "schema": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/SnapshotBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Snapshots" ], "summary": "Take a snapshot of the current live topology + entity state for a network.\nAcquires the discovery snapshot lock, creates the snapshots row, runs\nclose-and-clone to stamp every Snapshotable entity row with `snapshot_id`\nand close them. The topology subscriber inserts the snapshot's topology\nrow off the back of the `Snapshot::Created` event.", "operationId": "create_snapshot", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSnapshotRequest" } } }, "required": true }, "responses": { "200": { "description": "Snapshot created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Snapshot" } } } }, "402": { "description": "Snapshots not available on plan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Network is busy with discovery; retry shortly", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/snapshots/{id}": { "get": { "tags": [ "Snapshots" ], "summary": "Get a snapshot by ID.", "operationId": "get_snapshot_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Snapshot ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Snapshot found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Snapshot" } } } }, "404": { "description": "Snapshot not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Snapshots" ], "summary": "Delete a snapshot. The cascade FK on closed entity rows + topology rows\nreaps everything tied to this snapshot automatically.", "operationId": "delete_snapshot", "parameters": [ { "name": "id", "in": "path", "description": "Snapshot ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Snapshot deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Snapshot not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/subnets": { "get": { "tags": [ "Subnets" ], "summary": "List all subnets", "description": "Returns all subnets accessible to the authenticated user or daemon.\nDaemons can only access subnets within their assigned network.\nSupports pagination via `limit` and `offset` query parameters,\nand ordering via `group_by`, `order_by`, and `order_direction`.", "operationId": "list_subnets", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SubnetOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SubnetOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of subnets", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Subnet" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] }, { "daemon_api_key": [] } ] }, "post": { "tags": [ "Subnets" ], "summary": "Create a new subnet", "operationId": "create_subnet", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Subnet" } } }, "required": true }, "responses": { "200": { "description": "Subnet created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Subnet" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] }, { "daemon_api_key": [] } ] } }, "/api/v1/subnets/bulk-delete": { "post": { "tags": [ "Subnets" ], "summary": "Bulk delete Subnets", "operationId": "bulk_delete_subnets", "requestBody": { "description": "Array of Subnet IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Subnets deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/subnets/export/csv": { "get": { "tags": [ "Subnets" ], "summary": "Export Subnets to CSV", "description": "Export all Subnets matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_subnets_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SubnetOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SubnetOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Subnets", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/subnets/{id}": { "get": { "tags": [ "Subnets" ], "summary": "Get Subnet by ID", "operationId": "get_subnet_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Subnet ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Subnet found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Subnet" } } } }, "404": { "description": "Subnet not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Subnets" ], "summary": "Update a subnet", "description": "Updates subnet properties. If the CIDR is being changed, validates that\nall existing ip_addresses on this subnet have IPs within the new CIDR range.", "operationId": "update_subnet", "parameters": [ { "name": "id", "in": "path", "description": "Subnet ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Subnet" } } }, "required": true }, "responses": { "200": { "description": "Subnet updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Subnet" } } } }, "400": { "description": "CIDR change would orphan existing ip_addresses", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Subnet not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Subnets" ], "summary": "Delete Subnet", "operationId": "delete_subnet", "parameters": [ { "name": "id", "in": "path", "description": "Subnet ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Subnet deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Subnet not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags": { "get": { "tags": [ "Tags" ], "summary": "List all tags", "description": "Returns all tags in the authenticated user's organization.\nSupports pagination via `limit` and `offset` query parameters,\nand ordering via `group_by`, `order_by`, and `order_direction`.", "operationId": "get_all_tags", "parameters": [ { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TagOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TagOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of tags", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Tag" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Tags" ], "summary": "Create a new tag", "description": "Creates a tag scoped to your organization. Tag names must be unique within the organization.\n\n### Validation\n\n- Name must be 1-100 characters (empty names are rejected)\n- Name must be unique within your organization", "operationId": "create_tag", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Tag" } } }, "required": true }, "responses": { "200": { "description": "Tag created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Tag" } } } }, "400": { "description": "Validation error: name empty or too long", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Tag name already exists in this organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags/assign": { "put": { "tags": [ "Tags" ], "summary": "Set all tags for an entity", "description": "Replaces all tags on an entity with the provided list.\n\n### Validation\n\n- Entity type must be taggable (Host, Service, Subnet, Group, Network, Discovery, Daemon, DaemonApiKey, UserApiKey)\n- All tags must exist and belong to your organization", "operationId": "set_entity_tags", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetTagsRequest" } } }, "required": true }, "responses": { "200": { "description": "Tags set successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "400": { "description": "Invalid entity type or tag", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Tag not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags/assign/bulk-add": { "post": { "tags": [ "Tags" ], "summary": "Bulk add a tag to multiple entities", "description": "Adds a single tag to multiple entities of the same type. This is useful for batch tagging operations.\n\n### Validation\n\n- Entity type must be taggable (Host, Service, Subnet, Group, Network, Discovery, Daemon, DaemonApiKey, UserApiKey)\n- Tag must exist and belong to your organization\n- Entities that already have the tag are silently skipped", "operationId": "bulk_add_tag", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkTagRequest" } } }, "required": true }, "responses": { "200": { "description": "Tag added successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkTagResponse" } } } }, "400": { "description": "Invalid entity type or tag", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Tag not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags/assign/bulk-remove": { "post": { "tags": [ "Tags" ], "summary": "Bulk remove a tag from multiple entities", "description": "Removes a single tag from multiple entities of the same type.\n\n### Validation\n\n- Entity type must be taggable (Host, Service, Subnet, Group, Network, Discovery, Daemon, DaemonApiKey, UserApiKey)\n- Entities that don't have the tag are silently skipped", "operationId": "bulk_remove_tag", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkTagRequest" } } }, "required": true }, "responses": { "200": { "description": "Tag removed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkTagResponse" } } } }, "400": { "description": "Invalid entity type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags/bulk-delete": { "post": { "tags": [ "Tags" ], "summary": "Bulk delete Tags", "operationId": "bulk_delete_tags", "requestBody": { "description": "Array of Tag IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Tags deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags/export/csv": { "get": { "tags": [ "Tags" ], "summary": "Export Tags to CSV", "description": "Export all Tags matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_tags_csv", "parameters": [ { "name": "group_by", "in": "query", "description": "Primary ordering field (used for grouping). Always sorts ASC to keep groups together.", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TagOrderField" } ] } }, { "name": "order_by", "in": "query", "description": "Secondary ordering field (sorting within groups or standalone sort).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TagOrderField" } ] } }, { "name": "order_direction", "in": "query", "description": "Direction for order_by field (group_by always uses ASC).", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Tags", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/tags/{id}": { "get": { "tags": [ "Tags" ], "summary": "Get Tag by ID", "operationId": "get_tag_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Tag ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Tag found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Tag" } } } }, "404": { "description": "Tag not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Tags" ], "summary": "Update Tag", "operationId": "update_tag", "parameters": [ { "name": "id", "in": "path", "description": "Tag ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Tag" } } }, "required": true }, "responses": { "200": { "description": "Tag updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Tag" } } } }, "404": { "description": "Tag not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Tags" ], "summary": "Delete Tag", "operationId": "delete_tag", "parameters": [ { "name": "id", "in": "path", "description": "Tag ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Tag deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Tag not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/topology": { "get": { "tags": [ "Topologies", "internal" ], "summary": "Get all topologies for the authenticated user's networks.", "description": "Returns both live-view rows (`snapshot_id IS NULL`) and snapshot-pinned\nrows. The frontend renders the live one by default and renders snapshot\nrows when the user picks one from the snapshots dropdown.", "operationId": "get_all_topologies", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of topologies", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Topology" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/topology/data": { "get": { "tags": [ "Topologies", "internal" ], "summary": "Unified entity-set endpoint for the topology view.", "description": "`?snapshot_id=` resolves to the snapshot's `taken_at` and returns the\nas-of-T entity set; otherwise returns live entities. The frontend\n`TopologyTab` is the sole intended consumer.", "operationId": "get_topology_data", "parameters": [ { "name": "network_id", "in": "query", "description": "Network to read entities for. Required.", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "snapshot_id", "in": "query", "description": "When set, returns the entity set as it was when this snapshot was taken.\nWhen omitted, returns live entities.", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "mark_viewed", "in": "query", "description": "When `true`, records the `FirstTopologyRebuild` onboarding milestone (the user has\nviewed their topology). Only the frontend's explicit on-tab view sets this — the\nbackground topology-data query never does — so the milestone never fires from other\ntabs. One-time per org (guarded below + subscriber dedup).", "required": false, "schema": { "type": [ "boolean", "null" ] } } ], "responses": { "200": { "description": "Topology entity bundle", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_TopologyData" } } } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Snapshot not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/topology/export/csv": { "get": { "tags": [ "Topologies" ], "summary": "Export Topologies to CSV", "description": "Export all Topologies matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_topologies_csv", "parameters": [ { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "ids", "in": "query", "description": "Filter by specific entity IDs (for selective loading)", "required": false, "schema": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Topologies", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/topology/{id}": { "get": { "tags": [ "Topologies" ], "summary": "Get Topology by ID", "operationId": "get_topology_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Topology ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Topology found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Topology" } } } }, "404": { "description": "Topology not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Topologies", "internal" ], "operationId": "update_topology", "parameters": [ { "name": "id", "in": "path", "description": "Topology ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Topology" } } }, "required": true }, "responses": { "200": { "description": "Topology updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Topology" } } } }, "404": { "description": "Topology not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/topology/{id}/export/confluence": { "get": { "tags": [ "Topologies", "internal" ], "summary": "Export topology as Confluence wiki markup", "operationId": "export_confluence", "parameters": [ { "name": "id", "in": "path", "description": "Topology ID", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "view", "in": "query", "description": "View to export. Defaults to the default view when omitted.", "required": false, "schema": { "$ref": "#/components/schemas/TopologyView" } } ], "responses": { "200": { "description": "Confluence wiki markup export", "content": { "text/plain": {} } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Topology not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/topology/{id}/export/mermaid": { "get": { "tags": [ "Topologies", "internal" ], "summary": "Export topology as Mermaid flowchart", "operationId": "export_mermaid", "parameters": [ { "name": "id", "in": "path", "description": "Topology ID", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "view", "in": "query", "description": "View to export. Defaults to the default view when omitted.", "required": false, "schema": { "$ref": "#/components/schemas/TopologyView" } } ], "responses": { "200": { "description": "Mermaid flowchart export", "content": { "text/plain": {} } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "Topology not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/users": { "get": { "tags": [ "Users" ], "summary": "List all users", "operationId": "get_all_users", "parameters": [ { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "List of users", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_User" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/users/bulk-delete": { "post": { "tags": [ "Users" ], "summary": "Bulk delete users", "operationId": "bulk_delete_users", "requestBody": { "description": "Array of user IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Users deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } }, "403": { "description": "Cannot delete users with higher permissions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/users/export/csv": { "get": { "tags": [ "Users" ], "summary": "Export Users to CSV", "description": "Export all Users matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_users_csv", "parameters": [ { "name": "limit", "in": "query", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "description": "Number of results to skip. Default: 0.", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "CSV file containing Users", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/users/{id}": { "get": { "tags": [ "Users" ], "summary": "Get user by ID", "operationId": "get_user_by_id", "parameters": [ { "name": "id", "in": "path", "description": "User ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "User found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "403": { "description": "Access denied", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] }, "put": { "tags": [ "Users", "internal" ], "summary": "Update your own user record", "operationId": "update_user", "parameters": [ { "name": "id", "in": "path", "description": "User ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } }, "required": true }, "responses": { "200": { "description": "User updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "403": { "description": "Cannot update another user's record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "session": [] } ] }, "delete": { "tags": [ "Users" ], "summary": "Delete a user", "operationId": "delete_user", "parameters": [ { "name": "id", "in": "path", "description": "User ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "User deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "403": { "description": "Cannot delete user with higher permissions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "Cannot delete the only owner", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/users/{id}/admin": { "put": { "tags": [ "Users", "internal" ], "summary": "Admin update user (for changing permissions)", "operationId": "admin_update_user", "parameters": [ { "name": "id", "in": "path", "description": "User ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } }, "required": true }, "responses": { "200": { "description": "User updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_User" } } } }, "403": { "description": "Cannot update user with higher permissions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "404": { "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/vlans": { "get": { "tags": [ "Vlans" ], "summary": "List all VLANs", "description": "Returns VLANs accessible to the authenticated user, optionally filtered by network.", "operationId": "get_all_vlans", "parameters": [ { "name": "group_by", "in": "query", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/VlanOrderField" } ] } }, { "name": "order_by", "in": "query", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/VlanOrderField" } ] } }, { "name": "order_direction", "in": "query", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "List of VLANs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedApiResponse_Vlan" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "post": { "tags": [ "Vlans" ], "summary": "Create a new VLAN", "description": "Creates a VLAN scoped to a network. VLAN numbers must be unique within a network.", "operationId": "create_vlan", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Vlan" } } }, "required": true }, "responses": { "200": { "description": "VLAN created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vlan" } } } }, "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } }, "409": { "description": "VLAN number already exists in this network", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/vlans/bulk-delete": { "post": { "tags": [ "Vlans" ], "summary": "Bulk delete Vlans", "operationId": "bulk_delete_vlans", "requestBody": { "description": "Array of Vlan IDs to delete", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "required": true }, "responses": { "200": { "description": "Vlans deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_BulkDeleteResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/vlans/discovery": { "post": { "tags": [ "Vlans" ], "summary": "Bulk upsert VLANs from discovery", "description": "Used by daemons to report discovered VLANs. Creates new VLANs or updates names.\nReturns the mapping of VLAN numbers to entity UUIDs for Interface construction.", "operationId": "discovery_upsert_vlans", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VlanDiscoveryRequest" } } }, "required": true }, "responses": { "200": { "description": "VLANs upserted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_VlanDiscoveryResponse" } } } }, "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "daemon_api_key": [] } ] } }, "/api/v1/vlans/export/csv": { "get": { "tags": [ "Vlans" ], "summary": "Export Vlans to CSV", "description": "Export all Vlans matching the filter criteria to CSV format. Ignores pagination parameters (limit/offset) and exports all matching records.", "operationId": "export_vlans_csv", "parameters": [ { "name": "group_by", "in": "query", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/VlanOrderField" } ] } }, { "name": "order_by", "in": "query", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/VlanOrderField" } ] } }, { "name": "order_direction", "in": "query", "required": false, "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OrderDirection" } ] } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "maximum": 1000, "minimum": 0 } }, { "name": "offset", "in": "query", "required": false, "schema": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } }, { "name": "network_id", "in": "query", "description": "Filter by network ID", "required": false, "schema": { "type": [ "string", "null" ], "format": "uuid" } }, { "name": "at", "in": "query", "description": "As-of timestamp (ISO 8601). When set, returns SCD2 state as of this\ninstant (snapshot view) instead of live state.", "required": false, "schema": { "type": [ "string", "null" ], "format": "date-time" } } ], "responses": { "200": { "description": "CSV file containing Vlans", "content": { "text/csv": {} } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/v1/vlans/{id}": { "get": { "tags": [ "Vlans" ], "summary": "Get Vlan by ID", "operationId": "get_vlan_by_id", "parameters": [ { "name": "id", "in": "path", "description": "Vlan ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Vlan found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vlan" } } } }, "404": { "description": "Vlan not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "put": { "tags": [ "Vlans" ], "summary": "Update Vlan", "operationId": "update_vlan", "parameters": [ { "name": "id", "in": "path", "description": "Vlan ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Vlan" } } }, "required": true }, "responses": { "200": { "description": "Vlan updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_Vlan" } } } }, "404": { "description": "Vlan not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] }, "delete": { "tags": [ "Vlans" ], "summary": "Delete Vlan", "operationId": "delete_vlan", "parameters": [ { "name": "id", "in": "path", "description": "Vlan ID", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Vlan deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse" } } } }, "404": { "description": "Vlan not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiErrorResponse" } } } } }, "security": [ { "user_api_key": [] }, { "session": [] } ] } }, "/api/version": { "get": { "tags": [ "system", "internal" ], "summary": "Get API version information", "operationId": "get_version", "responses": { "200": { "description": "Version information", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiResponse_VersionInfo" } } } } } } } }, "components": { "schemas": { "ApiErrorResponse": { "type": "object", "description": "Error response type for API errors (no data field)", "required": [ "success", "meta" ], "properties": { "code": { "type": [ "string", "null" ], "description": "Machine-readable error code for i18n translation" }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta", "description": "API metadata (version info)" }, "params": { "type": [ "object", "null" ], "description": "Parameters for interpolating into the translated error message", "additionalProperties": {}, "propertyNames": { "type": "string" } }, "success": { "type": "boolean" } } }, "ApiMeta": { "type": "object", "description": "API metadata included in all responses", "required": [ "api_version", "server_version" ], "properties": { "api_version": { "type": "integer", "format": "int32", "description": "API version (integer, increments on breaking changes)", "minimum": 0 }, "server_version": { "type": "string", "description": "Server version (semver)", "example": "0.17.1" } }, "example": { "api_version": 1, "server_version": "0.17.1" } }, "ApiResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TupleUnit" } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Binding": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/BindingBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "description": "Association between a service and a port / interface that the service is listening on", "example": { "created_at": "2026-06-29T15:15:28.345331Z", "first_discovery_id": null, "id": "b0144d82-007f-4fb2-a690-da919ef47135", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.345331Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.345331Z", "valid_from": "2026-06-29T15:15:28.345331Z", "valid_to": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_BulkDeleteResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "required": [ "deleted_count", "requested_count" ], "properties": { "deleted_count": { "type": "integer", "minimum": 0 }, "requested_count": { "type": "integer", "minimum": 0 } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_BulkTagResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response for bulk tag operations", "required": [ "affected_count" ], "properties": { "affected_count": { "type": "integer", "description": "Number of entities affected", "minimum": 0 } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_CancelSubscriptionResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "required": [ "period_end" ], "properties": { "period_end": { "type": "string", "format": "date-time" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_ChangePlanPreview": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "required": [ "excess_hosts", "excess_networks", "excess_seats" ], "properties": { "excess_hosts": { "type": "integer", "format": "int64", "minimum": 0 }, "excess_networks": { "type": "integer", "format": "int64", "minimum": 0 }, "excess_seats": { "type": "integer", "format": "int64", "minimum": 0 } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Credential": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/CredentialBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_DaemonApiKey": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/DaemonApiKeyBase" }, { "type": "object", "required": [ "id", "updated_at", "created_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_DaemonApiKeyResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "required": [ "api_key", "key" ], "properties": { "api_key": { "$ref": "#/components/schemas/DaemonApiKey" }, "key": { "type": "string" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_DaemonRegistrationResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Daemon registration response from server to daemon", "required": [ "daemon", "host_id" ], "properties": { "daemon": { "$ref": "#/components/schemas/Daemon" }, "host_id": { "type": "string", "format": "uuid" }, "server_capabilities": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServerCapabilities", "description": "Server capabilities (returned if daemon sends version info)" } ] } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_DaemonResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/DaemonBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at", "version_status" ], "properties": { "created_at": { "type": "string", "format": "date-time" }, "id": { "type": "string", "format": "uuid" }, "updated_at": { "type": "string", "format": "date-time" }, "version_status": { "$ref": "#/components/schemas/DaemonVersionStatus", "description": "Computed version status including health and warnings" } } } ], "description": "Daemon response for UI including computed version status" }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_DashboardSummary": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Dashboard summary response", "required": [ "networks", "daemons", "recent_discoveries", "plan_usage" ], "properties": { "daemons": { "type": "array", "items": { "$ref": "#/components/schemas/DaemonResponse" } }, "networks": { "type": "array", "items": { "$ref": "#/components/schemas/NetworkSummary" } }, "plan_usage": { "$ref": "#/components/schemas/PlanUsage" }, "recent_discoveries": { "type": "array", "items": { "$ref": "#/components/schemas/Discovery" } } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Dependency": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/DependencyBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "color": "Blue", "created_at": "2026-01-15T10:30:00Z", "dependency_type": "RequestPath", "description": "HTTP/HTTPS services dependency", "edge_style": "Bezier", "id": "550e8400-e29b-41d4-a716-446655440008", "lineage_id": null, "members": { "service_ids": [], "type": "Services" }, "name": "Web Services", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Discovery": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/DiscoveryBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at", "integration_targets" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "force_full_scan": { "type": "boolean", "description": "When true, the next scan will be a full port scan regardless of interval" }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "integration_targets": { "type": "array", "items": { "$ref": "#/components/schemas/IntegrationTarget" }, "description": "Per-daemon integration targeting: which integrations (credentialed or credential-less\nlocal) run on this daemon, and on which IPs. Delivered via the init command at\nregistration and editable via the discovery modal. Persistent — re-applied every scan.\nThis is the single home for cred↔IP targeting; it replaces the global\n`credential.target_ips` (race-prone, consumed once) and the discovery modal's old\none-shot `pending_credential_ids`." }, "scan_count": { "type": "integer", "format": "int32", "description": "Number of completed scans (incremented by server on session completion)", "readOnly": true, "minimum": 0 }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_DiscoveryUpdatePayload": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Progress update from daemon to server during discovery", "required": [ "session_id", "daemon_id", "network_id", "phase", "discovery_type", "progress" ], "properties": { "daemon_id": { "type": "string", "format": "uuid" }, "discovery_id": { "type": [ "string", "null" ], "format": "uuid", "description": "The discovery configuration this session belongs to.\nAlways enriched server-side; daemons do not send this field." }, "discovery_type": { "$ref": "#/components/schemas/DiscoveryType" }, "error": { "type": [ "string", "null" ] }, "estimated_remaining_secs": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "finished_at": { "type": [ "string", "null" ], "format": "date-time" }, "hosts_discovered": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "network_id": { "type": "string", "format": "uuid" }, "phase": { "$ref": "#/components/schemas/DiscoveryPhase" }, "progress": { "type": "integer", "format": "int32", "minimum": 0 }, "scanned": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ScannedEntityIds", "description": "Canonical IDs of entities scanned in this session, populated daemon-\nside at terminal. **Transient**: stripped at `SqlValue::RunType` bind\ntime so it doesn't persist into the historical Discovery row's JSONB.\nAvailable in-memory through the `EntityOperation::Created` event\npublished for the historical Discovery row (the event scope carries\n`Entity::Discovery(...)`, the full in-memory struct), where per-entity\nFK-update subscribers consume it.\n\n`Some(...)` when the daemon is sending the terminal payload over the\nwire. `None` when read back from a persisted historical row, or when\nnot yet set." } ] }, "session_id": { "type": "string", "format": "uuid" }, "started_at": { "type": [ "string", "null" ], "format": "date-time" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_HostResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response type for host endpoints.\nIncludes children (ip_addresses, ports, services, interfaces).", "required": [ "id", "created_at", "updated_at", "name", "network_id", "source", "hidden", "tags", "ip_addresses", "ports", "services", "interfaces" ], "properties": { "chassis_id": { "type": [ "string", "null" ] }, "created_at": { "type": "string", "format": "date-time" }, "credential_assignments": { "type": "array", "items": { "$ref": "#/components/schemas/CredentialAssignment" } }, "description": { "type": [ "string", "null" ] }, "hidden": { "type": "boolean" }, "hostname": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" }, "description": "SNMP ifTable entries" }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "management_url": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } }, "source": { "$ref": "#/components/schemas/EntitySource" }, "sys_contact": { "type": [ "string", "null" ] }, "sys_descr": { "type": [ "string", "null" ] }, "sys_location": { "type": [ "string", "null" ] }, "sys_object_id": { "type": [ "string", "null" ] }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "updated_at": { "type": "string", "format": "date-time" }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostVirtualization" } ] } }, "example": { "created_at": "2026-01-15T10:30:00Z", "credential_assignments": [], "description": "Primary web server", "hidden": false, "hostname": "web-server-01.local", "id": "550e8400-e29b-41d4-a716-446655440003", "interfaces": [ { "admin_status": "Up", "cdp_address": null, "cdp_device_id": null, "cdp_platform": null, "cdp_port_id": null, "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-44665544000f", "if_alias": "Uplink to Core Switch", "if_descr": "GigabitEthernet0/1", "if_index": 1, "if_name": "Gi0/1", "if_type": 6, "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "lldp_chassis_id": null, "lldp_mgmt_addr": null, "lldp_port_desc": null, "lldp_port_id": null, "lldp_sys_desc": null, "lldp_sys_name": null, "mac_address": "DE:AD:BE:EF:CA:FE", "neighbor": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "oper_status": "Up", "speed_bps": 1000000000, "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "ip_addresses": [ { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440005", "ip_address": "192.168.1.100", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "mac_address": "DE:AD:BE:EF:CA:FE", "name": "eth0", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "subnet_id": "550e8400-e29b-41d4-a716-446655440004", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "name": "web-server-01", "network_id": "550e8400-e29b-41d4-a716-446655440002", "ports": [ { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440006", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "number": 80, "protocol": "Tcp", "type": "Http", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "services": [ { "bindings": [ { "created_at": "2026-06-29T15:15:28.325514Z", "first_discovery_id": null, "id": "bcfb3d6e-4028-4022-81e5-f17eb48c1b55", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.325514Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.325514Z", "valid_from": "2026-06-29T15:15:28.325514Z", "valid_to": null } ], "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440007", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "nginx", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "service_definition": "Frigate", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } ], "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "virtualization": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_IPAddress": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/IPAddressBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440005", "ip_address": "192.168.1.100", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "mac_address": "DE:AD:BE:EF:CA:FE", "name": "eth0", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "subnet_id": "550e8400-e29b-41d4-a716-446655440004", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Interface": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/InterfaceBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Invite": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/InviteBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Network": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/NetworkBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ], "example": { "created_at": "2026-01-15T10:30:00Z", "credential_ids": [], "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Home Network", "organization_id": "550e8400-e29b-41d4-a716-446655440001", "tags": [], "updated_at": "2026-01-15T10:30:00Z" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_OnboardingStateResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response from onboarding state endpoint", "properties": { "network": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OnboardingNetworkState", "description": "Network from pending setup (with name and ID)" } ] }, "network_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Network ID from pending setup (if any)" }, "org_name": { "type": [ "string", "null" ], "description": "Organization name from pending setup" }, "step": { "type": [ "string", "null" ], "description": "Current onboarding step (if any)" }, "use_case": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/UseCase", "description": "Use case selection (homelab, company, msp)" } ] } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Option_SaveOfferCoupon": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "oneOf": [ { "type": "null" }, { "type": "object", "description": "Live terms for the configured save-offer coupon, read directly from\nStripe. Used by the cancel modal's Discount panel to render the offer\ndynamically instead of hard-coding the percent/duration.\n\nOnly returned when the coupon would actually catch the user's next\ninvoice — i.e. `next_renewal_at` falls within the coupon's `duration_in_months`\nwindow. Yearly subscribers partway through a cycle whose next renewal\nlands after the coupon's window get `None` from the endpoint and the\ncancel modal's Discount panel doesn't render.\n\n`billing_rate` lets the frontend pick monthly vs yearly copy: a monthly\nsubscriber thinks in terms of \"N months of discount\"; a yearly subscriber\nthinks in terms of \"my next renewal on {date}.\"", "required": [ "percent_off", "duration_in_months", "next_renewal_at", "billing_rate" ], "properties": { "billing_rate": { "$ref": "#/components/schemas/BillingRate" }, "duration_in_months": { "type": "integer", "format": "int64" }, "next_renewal_at": { "type": "string", "format": "date-time" }, "percent_off": { "type": "integer", "format": "int64" } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Organization": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/OrganizationBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Port": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/PortBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "description": "Port entity with custom serialization that flattens PortType fields.", "example": { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440006", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "number": 80, "protocol": "Tcp", "type": "Http", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_ProvisionDaemonResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response from provisioning a daemon.\nContains the daemon record and the API key (shown only once).", "required": [ "daemon", "daemon_api_key" ], "properties": { "daemon": { "$ref": "#/components/schemas/DaemonResponse", "description": "The created daemon record (with version status)." }, "daemon_api_key": { "type": "string", "description": "The API key (plaintext) for daemon authentication.\nThis is shown only once - store it securely." } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_PublicConfigResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "required": [ "server_port", "disable_registration", "disable_password_login", "oidc_providers", "billing_enabled", "discount_save_offer_available", "has_integrated_daemon", "has_email_service", "has_email_opt_in", "public_url", "needs_cookie_consent", "deployment_type", "license_in_grace_period" ], "properties": { "billing_enabled": { "type": "boolean" }, "deployment_type": { "$ref": "#/components/schemas/DeploymentType" }, "disable_password_login": { "type": "boolean" }, "disable_registration": { "type": "boolean" }, "discount_save_offer_available": { "type": "boolean", "description": "`STRIPE_SAVE_OFFER_COUPON_ID` env var is set. When false, the\ncancel modal hides the discount save-offer panel so the user\ndoesn't see an option the deployment can't fulfil." }, "has_email_opt_in": { "type": "boolean" }, "has_email_service": { "type": "boolean" }, "has_integrated_daemon": { "type": "boolean" }, "license_expiry": { "type": [ "string", "null" ], "description": "Hard expiry — the drop-dead date after which the server rejects\nthe key. Referenced by the grace-period banner." }, "license_in_grace_period": { "type": "boolean", "description": "True when the license is past `intended_exp` but not yet past\nthe hard `exp` — the silent grace window." }, "license_intended_expiry": { "type": [ "string", "null" ], "description": "User-visible expiry — the date displayed to end users under\nnormal operation. 7 days earlier than `license_expiry` for keys\nissued after grace-period support landed." }, "license_status": { "type": [ "string", "null" ] }, "needs_cookie_consent": { "type": "boolean" }, "oidc_providers": { "type": "array", "items": { "$ref": "#/components/schemas/OidcProviderMetadata" } }, "posthog_key": { "type": [ "string", "null" ] }, "public_url": { "type": "string" }, "server_port": { "type": "integer", "format": "int32", "minimum": 0 }, "snapshot_retention_days_override": { "type": [ "integer", "null" ], "format": "int32", "description": "`SCANOPY_SNAPSHOT_RETENTION_DAYS_OVERRIDE` if set on this instance.\nFrontend uses it inside the plan-comparison view to display the\neffective retention for this deployment rather than the per-plan\nfixture default.", "minimum": 0 }, "stripe_publishable_key": { "type": [ "string", "null" ], "description": "Stripe publishable key, exposed so the frontend can mount Stripe\nElements (Payment Element) for in-app card collection. `None` when\nbilling isn't configured. Publishable keys are safe to expose to the\nbrowser (same as `posthog_key`)." } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_PublicShareMetadata": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Public share metadata (returned without authentication)", "required": [ "id", "name", "requires_password", "options", "enabled_views" ], "properties": { "enabled_views": { "type": "array", "items": { "$ref": "#/components/schemas/TopologyView" }, "description": "Resolved list of available topology views for this share.\nFiltered by both share configuration and data availability.\nFirst element is the default view." }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "options": { "$ref": "#/components/schemas/ShareOptions" }, "requires_password": { "type": "boolean" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_ServerCapabilities": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Server capabilities returned on startup/registration", "required": [ "server_version", "minimum_daemon_version" ], "properties": { "deprecation_warnings": { "type": "array", "items": { "$ref": "#/components/schemas/DeprecationWarning" }, "description": "Deprecation warnings for the daemon" }, "minimum_daemon_version": { "type": "string", "description": "Minimum daemon version supported by this server" }, "server_version": { "type": "string", "description": "Server software version" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Service": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/ServiceBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "bindings": [ { "created_at": "2026-06-29T15:15:28.339152Z", "first_discovery_id": null, "id": "9038c85e-20dc-4feb-b435-118f05763ce0", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.339152Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.339152Z", "valid_from": "2026-06-29T15:15:28.339152Z", "valid_to": null } ], "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440007", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "nginx", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "service_definition": "Frigate", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_SetupIntentResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response for creating a SetupIntent — the client secret the frontend\nPayment Element uses to collect and confirm a card in-app.", "required": [ "client_secret" ], "properties": { "client_secret": { "type": "string" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_SetupResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response from setup endpoint", "required": [ "network_id" ], "properties": { "network_id": { "type": "string", "format": "uuid" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Share": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/ShareBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_ShareAccessTokenResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Access token returned after successful password verification.\n\nThe token is an HS256 JWT tied to the share's `password_hash` — changing\nthe share password implicitly invalidates all outstanding tokens.", "required": [ "access_token", "expires_at" ], "properties": { "access_token": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Snapshot": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/SnapshotBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_String": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "string" }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Subnet": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/SubnetBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "cidr": "192.168.1.0/24", "created_at": "2026-01-15T10:30:00Z", "description": "Local area network", "first_discovery_id": null, "id": "550e8400-e29b-41d4-a716-446655440004", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "LAN", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "subnet_type": "Lan", "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Tag": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/TagBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "color": "Green", "created_at": "2026-01-15T10:30:00Z", "description": "Production environment resources", "id": "550e8400-e29b-41d4-a716-44665544000a", "is_application": false, "lineage_id": null, "name": "production", "organization_id": "550e8400-e29b-41d4-a716-446655440001", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_TestReachabilityResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response from a reachability test.", "required": [ "reachable" ], "properties": { "error": { "type": [ "string", "null" ], "description": "Error message if not reachable" }, "health": { "type": [ "boolean", "null" ], "description": "Health check result (only present when check_health was true)" }, "reachable": { "type": "boolean", "description": "Whether the TCP connection succeeded" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Topology": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/TopologyBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_TopologyData": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Bundle of entities + the built graph that feed the topology render, export,\nand share pipelines.\n\nLoaded by [`crate::server::topology::service::main::TopologyService::get_topology_data`]\nfor either the live view (`snapshot_id = None`) or a point-in-time snapshot\n(`snapshot_id = Some(id)`). The per-view `nodes`/`edges` are built on request\nfrom these entities + the network's grouping options\n(`build_all_view_graphs`) — they are not persisted. The frontend selects the\nactive view's slice client-side.", "required": [ "hosts", "ip_addresses", "subnets", "dependencies", "ports", "bindings", "interfaces", "services", "vlans", "tags" ], "properties": { "available_views": { "type": "array", "items": { "$ref": "#/components/schemas/TopologyView" }, "description": "Views whose data is present in this entity set (L3/Workloads always;\nL2 Physical iff LLDP/CDP neighbors exist; Application iff app-flagged\ntags are used). The topology tab restricts a snapshot's view picker to\nthese — you can't set up SNMP or create app tags on a historical\nsnapshot — while the live view shows all views with setup prompts." }, "bindings": { "type": "array", "items": { "$ref": "#/components/schemas/Binding" } }, "dependencies": { "type": "array", "items": { "$ref": "#/components/schemas/Dependency" } }, "edges": { "type": "object", "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/Edge" } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } }, "hosts": { "type": "array", "items": { "$ref": "#/components/schemas/Host" } }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" } }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "nodes": { "type": "object", "description": "Per-view graph built on request from the entities above + grouping\noptions. Keyed by view so switching the active perspective is a\nclient-side slice selection.", "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/Node" } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } }, "subnets": { "type": "array", "items": { "$ref": "#/components/schemas/Subnet" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } }, "vlans": { "type": "array", "items": { "$ref": "#/components/schemas/Vlan" } } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_User": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/UserBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_UserApiKey": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/UserApiKeyBase" }, { "type": "object", "required": [ "id", "updated_at", "created_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_UserApiKeyResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response for user API key creation/rotation\nContains the full API key record plus the plaintext key (shown only once)", "required": [ "api_key", "key" ], "properties": { "api_key": { "$ref": "#/components/schemas/UserApiKey" }, "key": { "type": "string", "description": "The plaintext API key - only returned once during creation or rotation" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Vec_BillingPlan": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "array", "items": { "oneOf": [ { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Community" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Free" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Starter" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Pro" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Team" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Business" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Enterprise" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Demo" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "CommercialSelfHosted" ] } } } ] } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Vec_Credential": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/CredentialBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Vec_DiscoveryUpdatePayload": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "array", "items": { "type": "object", "description": "Progress update from daemon to server during discovery", "required": [ "session_id", "daemon_id", "network_id", "phase", "discovery_type", "progress" ], "properties": { "daemon_id": { "type": "string", "format": "uuid" }, "discovery_id": { "type": [ "string", "null" ], "format": "uuid", "description": "The discovery configuration this session belongs to.\nAlways enriched server-side; daemons do not send this field." }, "discovery_type": { "$ref": "#/components/schemas/DiscoveryType" }, "error": { "type": [ "string", "null" ] }, "estimated_remaining_secs": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "finished_at": { "type": [ "string", "null" ], "format": "date-time" }, "hosts_discovered": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "network_id": { "type": "string", "format": "uuid" }, "phase": { "$ref": "#/components/schemas/DiscoveryPhase" }, "progress": { "type": "integer", "format": "int32", "minimum": 0 }, "scanned": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ScannedEntityIds", "description": "Canonical IDs of entities scanned in this session, populated daemon-\nside at terminal. **Transient**: stripped at `SqlValue::RunType` bind\ntime so it doesn't persist into the historical Discovery row's JSONB.\nAvailable in-memory through the `EntityOperation::Created` event\npublished for the historical Discovery row (the event scope carries\n`Entity::Discovery(...)`, the full in-memory struct), where per-entity\nFK-update subscribers consume it.\n\n`Some(...)` when the daemon is sending the terminal payload over the\nwire. `None` when read back from a persisted historical row, or when\nnot yet set." } ] }, "session_id": { "type": "string", "format": "uuid" }, "started_at": { "type": [ "string", "null" ], "format": "date-time" } } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Vec_Invite": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/InviteBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_VersionInfo": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Version information for API compatibility checking", "required": [ "api_version", "server_version" ], "properties": { "api_version": { "type": "integer", "format": "int32", "description": "Current API version (integer, increments on breaking changes)", "minimum": 0 }, "min_compatible_client": { "type": [ "string", "null" ], "description": "Minimum client version that can use this API (optional, for future use)" }, "server_version": { "type": "string", "description": "Server version (semver)", "example": "0.12.10" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_Vlan": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "allOf": [ { "$ref": "#/components/schemas/VlanBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ] }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_VlanDiscoveryResponse": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "object", "description": "Response for discovery upsert", "required": [ "vlans" ], "properties": { "vlans": { "type": "array", "items": { "$ref": "#/components/schemas/VlanDiscoveryResponseItem" }, "description": "Mapping of vlan_number → VLAN entity UUID" } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "ApiResponse_u32": { "type": "object", "required": [ "success", "meta" ], "properties": { "data": { "type": "integer", "format": "int32", "minimum": 0 }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/ApiMeta" }, "success": { "type": "boolean" } } }, "BillingPlan": { "oneOf": [ { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Community" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Free" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Starter" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Pro" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Team" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Business" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Enterprise" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Demo" ] } } } ] }, { "allOf": [ { "$ref": "#/components/schemas/PlanConfig" }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "CommercialSelfHosted" ] } } } ] } ] }, "BillingRate": { "type": "string", "enum": [ "Month", "Year" ] }, "Binding": { "allOf": [ { "$ref": "#/components/schemas/BindingBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "description": "Association between a service and a port / interface that the service is listening on", "example": { "created_at": "2026-06-29T15:15:28.325957Z", "first_discovery_id": null, "id": "38eff216-2ec5-4bdc-926b-2fdfcfa549f1", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.325957Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.325957Z", "valid_from": "2026-06-29T15:15:28.325957Z", "valid_to": null } }, "BindingBase": { "allOf": [ { "$ref": "#/components/schemas/BindingType" }, { "type": "object", "required": [ "service_id", "network_id" ], "properties": { "network_id": { "type": "string", "format": "uuid" }, "service_id": { "type": "string", "format": "uuid" } } } ], "description": "The base data for a Binding entity (everything except id, created_at, updated_at)" }, "BindingInput": { "oneOf": [ { "type": "object", "title": "IPAddress", "description": "Bind to an interface (service is present at this interface without a specific port)", "required": [ "id", "ip_address_id", "type" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Client-provided UUID for this binding" }, "ip_address_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "IPAddress" ] } } }, { "type": "object", "title": "Port", "description": "Bind to a port (optionally on a specific ip_address)", "required": [ "id", "port_id", "type" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Client-provided UUID for this binding" }, "ip_address_id": { "type": [ "string", "null" ], "format": "uuid", "description": "null = bind to all ip_addresses" }, "port_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "Port" ] } } } ], "description": "Input for creating or updating a binding within a service.\nUsed in both CreateHostRequest and UpdateHostRequest.\nClient must provide a UUID for the binding." }, "BindingType": { "oneOf": [ { "type": "object", "title": "IPAddress", "description": "IP address binding: Service is present at an IP address without a specific port.\nUsed for non-port-bound services like gateways. Conflicts with port bindings on the same IP address.", "required": [ "ip_address_id", "type" ], "properties": { "ip_address_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "IPAddress" ] } } }, { "type": "object", "title": "Port", "description": "Port binding: Service listens on a specific port, optionally on a specific IP address.\nIf `ip_address_id` is `null`, the service listens on this port across all IP addresses,\nwhich supersedes any specific-IP-address bindings for the same port.", "required": [ "port_id", "ip_address_id", "type" ], "properties": { "ip_address_id": { "type": [ "string", "null" ], "format": "uuid", "description": "The IP address this port binding applies to. If `null`, the binding applies to all\nIP addresses on the host (and supersedes specific-IP-address bindings for this port)." }, "port_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "Port" ] } } } ], "description": "The type of binding - either to an interface or to a port.\n\nBindings associate a service with network resources (ip_addresses/ports) on a host.\n\n## Validation Rules\n\n- All bindings must reference ports/interfaces that belong to the same host as the service.\n- Interface bindings conflict with port bindings on the same interface.\n- A port binding on all ip_addresses (`ip_address_id: null`) conflicts with any interface binding.\n- When a port binding with `ip_address_id: null` is created, it supersedes (removes) any\n existing specific-interface bindings for the same port." }, "BulkDeleteResponse": { "type": "object", "required": [ "deleted_count", "requested_count" ], "properties": { "deleted_count": { "type": "integer", "minimum": 0 }, "requested_count": { "type": "integer", "minimum": 0 } } }, "BulkTagRequest": { "type": "object", "description": "Request body for bulk tag operations", "required": [ "entity_type", "entity_ids", "tag_id" ], "properties": { "entity_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "The IDs of entities to modify" }, "entity_type": { "$ref": "#/components/schemas/EntityDiscriminants", "description": "The entity type (e.g., Host, Service, Subnet)" }, "tag_id": { "type": "string", "format": "uuid", "description": "The tag ID to add or remove" } } }, "BulkTagResponse": { "type": "object", "description": "Response for bulk tag operations", "required": [ "affected_count" ], "properties": { "affected_count": { "type": "integer", "description": "Number of entities affected", "minimum": 0 } } }, "CancelReason": { "type": "string", "description": "Cancellation reason captured in `SubscriptionCancelled` /\n`CancellationInitiated` events. Mirrors the values surfaced in the\nin-app cancel flow (Phase 5).", "enum": [ "too_expensive", "missing_features", "switched_service", "unused", "customer_service", "low_quality", "too_complex", "other" ] }, "CancelSubscriptionRequest": { "type": "object", "required": [ "reason_code" ], "properties": { "comment": { "type": [ "string", "null" ] }, "reason_code": { "$ref": "#/components/schemas/CancelReason" }, "save_offer_redeemed": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SaveOffer" } ] }, "save_offer_shown": { "type": "array", "items": { "$ref": "#/components/schemas/SaveOffer" } } } }, "CancelSubscriptionResponse": { "type": "object", "required": [ "period_end" ], "properties": { "period_end": { "type": "string", "format": "date-time" } } }, "ChangePlanPreview": { "type": "object", "required": [ "excess_hosts", "excess_networks", "excess_seats" ], "properties": { "excess_hosts": { "type": "integer", "format": "int64", "minimum": 0 }, "excess_networks": { "type": "integer", "format": "int64", "minimum": 0 }, "excess_seats": { "type": "integer", "format": "int64", "minimum": 0 } } }, "ChangePlanRequest": { "type": "object", "required": [ "plan", "rate" ], "properties": { "plan": { "$ref": "#/components/schemas/BillingPlan" }, "rate": { "$ref": "#/components/schemas/BillingRate" } } }, "CheckEmailRequest": { "type": "object", "description": "Check email availability request", "required": [ "email" ], "properties": { "email": { "type": "string", "format": "email" } } }, "Color": { "type": "string", "enum": [ "Pink", "Rose", "Red", "Amber", "Orange", "Green", "Emerald", "Teal", "Cyan", "Blue", "Indigo", "Purple", "Fuchsia", "Violet", "Sky", "Gray", "Lime", "Yellow" ] }, "ContainerType": { "type": "string", "enum": [ "Subnet", "ServiceCategory", "Application", "Root", "Host", "NestedTag", "NestedServiceCategory", "Hypervisor", "ContainerRuntime", "Stack", "TrunkPort", "VLAN", "PortOpStatus" ] }, "CreateBindingInput": { "oneOf": [ { "type": "object", "title": "IPAddress", "description": "Bind to an interface (service listens on all ports on this ip_address)", "required": [ "ip_address_id", "type" ], "properties": { "ip_address_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "IPAddress" ] } } }, { "type": "object", "title": "Port", "description": "Bind to a port (optionally on a specific ip_address)", "required": [ "port_id", "type" ], "properties": { "ip_address_id": { "type": [ "string", "null" ], "format": "uuid" }, "port_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "Port" ] } } } ], "description": "Input for creating a binding with a service.\n`service_id` and `network_id` are assigned by the server after the service is created." }, "CreateCheckoutRequest": { "type": "object", "required": [ "plan", "url" ], "properties": { "plan": { "$ref": "#/components/schemas/BillingPlan" }, "url": { "type": "string" } } }, "CreateHostRequest": { "type": "object", "description": "Request type for creating a host with its associated ip_addresses, ports, and services.\nServer assigns `host_id`, `network_id`, and `source` to all children.\nClient must provide UUIDs for all entities, enabling services to reference\nip_addresses/ports by ID in the same request.", "required": [ "name", "network_id", "tags" ], "properties": { "chassis_id": { "type": [ "string", "null" ] }, "credential_assignments": { "type": "array", "items": { "$ref": "#/components/schemas/CredentialAssignment" } }, "description": { "type": [ "string", "null" ] }, "hidden": { "type": "boolean" }, "hostname": { "type": [ "string", "null" ] }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/InterfaceInput" }, "description": "SNMP interface entries (ifTable data) - server assigns UUIDs" }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddressInput" }, "description": "Interfaces to create with this host (client provides UUIDs)" }, "management_url": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/PortInput" }, "description": "Ports to create with this host (client provides UUIDs)" }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceInput" }, "description": "Services to create with this host (can reference ip_addresses/ports by their UUIDs)" }, "sys_contact": { "type": [ "string", "null" ] }, "sys_descr": { "type": [ "string", "null" ] }, "sys_location": { "type": [ "string", "null" ] }, "sys_object_id": { "type": [ "string", "null" ] }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostVirtualization" } ] } }, "example": { "credential_assignments": [], "description": "Primary web server", "hidden": false, "hostname": "web-server-01.local", "interfaces": [], "ip_addresses": [ { "id": "550e8400-e29b-41d4-a716-446655440005", "ip_address": "192.168.1.100", "mac_address": "DE:AD:BE:EF:12:34", "name": "eth0", "position": 0, "subnet_id": "550e8400-e29b-41d4-a716-446655440004" } ], "name": "web-server-01", "network_id": "550e8400-e29b-41d4-a716-446655440002", "ports": [ { "id": "550e8400-e29b-41d4-a716-446655440006", "number": 80, "protocol": "Tcp" } ], "services": [ { "bindings": [ { "id": "550e8400-e29b-41d4-a716-446655440009", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "port_id": "550e8400-e29b-41d4-a716-446655440006", "type": "Port" } ], "id": "550e8400-e29b-41d4-a716-446655440007", "name": "nginx", "position": 0, "service_definition": "Frigate", "tags": [], "virtualization": null } ], "tags": [], "virtualization": null } }, "CreateInviteRequest": { "type": "object", "required": [ "permissions", "network_ids" ], "properties": { "expiration_hours": { "type": [ "integer", "null" ], "format": "int64" }, "network_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "permissions": { "$ref": "#/components/schemas/UserOrgPermissions" }, "send_to": { "type": [ "string", "null" ] } } }, "CreateServiceRequest": { "type": "object", "description": "Request type for creating a service.\nServer assigns `id`, `created_at`, `updated_at`, and `source`.\nServer also assigns `service_id` and `network_id` to all bindings.", "required": [ "host_id", "network_id", "service_definition", "name", "tags" ], "properties": { "bindings": { "type": "array", "items": { "$ref": "#/components/schemas/CreateBindingInput" }, "description": "Bindings to create with the service.\n`service_id` and `network_id` are assigned by the server." }, "host_id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "service_definition": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceVirtualization" } ] } } }, "CreateSnapshotRequest": { "type": "object", "required": [ "network_id" ], "properties": { "network_id": { "type": "string", "format": "uuid" } } }, "CreateUpdateShareRequest": { "type": "object", "required": [ "share" ], "properties": { "share": { "$ref": "#/components/schemas/Share" } } }, "Credential": { "allOf": [ { "$ref": "#/components/schemas/CredentialBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "CredentialAssignment": { "type": "object", "description": "A credential assigned to a host, optionally limited to specific ip_addresses.", "required": [ "credential_id", "ip_address_ids" ], "properties": { "credential_id": { "type": "string", "format": "uuid" }, "ip_address_ids": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" }, "description": "Interface IDs to limit this credential to. None = all host ip_addresses." } } }, "CredentialBase": { "type": "object", "required": [ "organization_id", "name", "credential_type", "tags", "assigned_network_ids", "host_assignments" ], "properties": { "assigned_network_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Networks this credential is assigned to (Broadcast scope).\nHydrated from the `network_credentials` junction table." }, "credential_type": { "$ref": "#/components/schemas/CredentialType" }, "host_assignments": { "type": "array", "items": { "$ref": "#/components/schemas/CredentialHostAssignment" }, "description": "Hosts this credential is assigned to (PerHost scope), with optional IP scoping.\nHydrated from the `host_credentials` junction table." }, "name": { "type": "string" }, "organization_id": { "type": "string", "format": "uuid" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "target_ips": { "type": [ "array", "null" ], "items": { "type": "string" }, "description": "Ephemeral bootstrap IPs for pre-discovery credential resolution.\nCleared automatically when the next scan dispatches." } } }, "CredentialHostAssignment": { "type": "object", "description": "Host-keyed mirror of [`CredentialAssignment`]: a host this credential is\nassigned to, optionally limited to specific ip_addresses. Hydrated onto a\ncredential from the `host_credentials` junction (PerHost scope).", "required": [ "host_id", "ip_address_ids" ], "properties": { "host_id": { "type": "string", "format": "uuid" }, "ip_address_ids": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" }, "description": "IP address IDs to limit this credential to on the host. None = all host ip_addresses." } } }, "CredentialOrderField": { "type": "string", "enum": [ "created_at", "name", "updated_at" ] }, "CredentialType": { "oneOf": [ { "type": "object", "description": "SNMPv1 community string — for legacy devices that only speak v1.", "required": [ "community", "type" ], "properties": { "community": { "$ref": "#/components/schemas/SecretValue" }, "type": { "type": "string", "enum": [ "SnmpV1" ] } } }, { "type": "object", "description": "SNMPv2c community string for querying network devices", "required": [ "community", "type" ], "properties": { "community": { "$ref": "#/components/schemas/SecretValue" }, "type": { "type": "string", "enum": [ "SnmpV2c" ] } } }, { "type": "object", "description": "SNMPv3 USM AuthPriv — security name + auth/priv protocols and passwords.", "required": [ "security_name", "auth_protocol", "auth_password", "priv_protocol", "priv_password", "type" ], "properties": { "auth_password": { "$ref": "#/components/schemas/SecretValue" }, "auth_protocol": { "$ref": "#/components/schemas/SnmpV3AuthProtocol" }, "context_name": { "type": [ "string", "null" ], "description": "Optional context name (default/empty context used if unset)." }, "priv_password": { "$ref": "#/components/schemas/SecretValue" }, "priv_protocol": { "$ref": "#/components/schemas/SnmpV3PrivProtocol" }, "security_name": { "type": "string" }, "type": { "type": "string", "enum": [ "SnmpV3" ] } } }, { "type": "object", "description": "Docker API proxy credentials. Target IP determined from host ip_addresses at scan time.", "required": [ "type" ], "properties": { "path": { "type": [ "string", "null" ], "description": "Optional URL path prefix (e.g. \"/v1.43\")" }, "port": { "type": "integer", "format": "int32", "description": "Port for the Docker API proxy (default 2375)", "minimum": 0 }, "ssl_cert": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/FileOrInline", "description": "PEM-encoded public certificate — inline or file path on daemon host" } ] }, "ssl_chain": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/FileOrInline", "description": "PEM-encoded CA chain — inline or file path on daemon host" } ] }, "ssl_key": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SecretValue", "description": "Private key — inline PEM content or file path on daemon host" } ] }, "type": { "type": "string", "enum": [ "DockerProxy" ] } } }, { "type": "object", "description": "Local Docker socket access on the daemon host. `socket_path` optionally repoints the\nsocket (non-default `DOCKER_HOST`); blank ⇒ the daemon auto-detects (bollard defaults).", "required": [ "type" ], "properties": { "socket_path": { "type": [ "string", "null" ] }, "type": { "type": "string", "enum": [ "DockerSocket" ] } } }, { "type": "object", "description": "Podman API proxy credentials. Podman exposes a Docker-compatible REST API,\nso the fields mirror `DockerProxy`. Target IP determined from host\nip_addresses at scan time.", "required": [ "type" ], "properties": { "path": { "type": [ "string", "null" ], "description": "Optional URL path prefix (e.g. \"/v1.43\")" }, "port": { "type": "integer", "format": "int32", "description": "Port for the Podman API proxy (default 2375)", "minimum": 0 }, "ssl_cert": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/FileOrInline", "description": "PEM-encoded public certificate — inline or file path on daemon host" } ] }, "ssl_chain": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/FileOrInline", "description": "PEM-encoded CA chain — inline or file path on daemon host" } ] }, "ssl_key": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SecretValue", "description": "Private key — inline PEM content or file path on daemon host" } ] }, "type": { "type": "string", "enum": [ "PodmanProxy" ] } } }, { "type": "object", "description": "Local Podman socket access on the daemon host. `socket_path` optionally repoints the\nsocket (e.g. rootful `/run/podman/podman.sock` vs rootless\n`$XDG_RUNTIME_DIR/podman/podman.sock`); blank ⇒ the daemon auto-detects via\n`resolve_podman_socket_path()`.", "required": [ "type" ], "properties": { "socket_path": { "type": [ "string", "null" ] }, "type": { "type": "string", "enum": [ "PodmanSocket" ] } } } ], "description": "Universal credential type — tagged enum stored as JSONB.\nEach variant represents a different credential protocol/method." }, "Daemon": { "allOf": [ { "$ref": "#/components/schemas/DaemonBase" }, { "type": "object", "required": [ "id", "updated_at", "created_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "DaemonApiKey": { "allOf": [ { "$ref": "#/components/schemas/DaemonApiKeyBase" }, { "type": "object", "required": [ "id", "updated_at", "created_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "DaemonApiKeyBase": { "type": "object", "required": [ "key", "name", "last_used", "network_id", "tags" ], "properties": { "expires_at": { "type": [ "string", "null" ], "format": "date-time" }, "is_enabled": { "type": "boolean" }, "key": { "type": "string", "readOnly": true }, "last_used": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "DaemonApiKeyResponse": { "type": "object", "required": [ "api_key", "key" ], "properties": { "api_key": { "$ref": "#/components/schemas/DaemonApiKey" }, "key": { "type": "string" } } }, "DaemonBase": { "type": "object", "required": [ "host_id", "network_id", "url", "capabilities", "mode", "name", "tags", "user_id" ], "properties": { "api_key_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Foreign key to API key used for ServerPoll authentication.\nNULL for DaemonPoll daemons or those not yet linked to a key." }, "capabilities": { "$ref": "#/components/schemas/DaemonCapabilities" }, "host_id": { "type": "string", "format": "uuid" }, "is_unreachable": { "type": "boolean", "description": "Whether the daemon is unreachable (for ServerPoll circuit breaker).\nSet to true after repeated polling failures, reset via retry-connection endpoint." }, "last_seen": { "type": [ "string", "null" ], "format": "date-time", "description": "Timestamp of last successful contact with daemon.\nNULL for provisioned ServerPoll daemons that haven't been contacted yet.", "readOnly": true }, "mode": { "$ref": "#/components/schemas/DaemonMode" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "standby": { "type": "boolean", "description": "Whether the daemon is on standby due to inactivity (no discovery in 30 days).", "readOnly": true }, "standby_cleared_at": { "type": [ "string", "null" ], "format": "date-time", "description": "Timestamp of the most recent standby → active transition. Set by\n`process_startup` when a restarted daemon is un-standby'd, and by\nthe discovery auto-wake path. The nightly inactivity check skips\ndaemons within the grace window (see `STANDBY_GRACE_PERIOD_DAYS`)\nto prevent the \"restart → cleared → re-standby'd before discovery\nruns\" race.", "readOnly": true }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "url": { "type": "string", "readOnly": true }, "user_id": { "type": "string", "format": "uuid", "description": "User responsible for maintaining this daemon" }, "version": { "type": [ "string", "null" ], "description": "Daemon software version (semver format)" } } }, "DaemonCapabilities": { "type": "object", "description": "Daemon capabilities", "required": [ "interfaced_subnet_ids" ], "properties": { "interfaced_subnet_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "DaemonHeartbeatPayload": { "type": "object", "description": "Legacy heartbeat payload for backwards compatibility with pre-v0.14.0 daemons.\nOld daemons call POST /api/daemons/{id}/heartbeat with this payload.", "required": [ "url", "name", "mode" ], "properties": { "mode": { "$ref": "#/components/schemas/DaemonMode" }, "name": { "type": "string" }, "url": { "type": "string" } } }, "DaemonMode": { "type": "string", "description": "Daemon operating mode that determines the communication pattern.\n\n- **DaemonPoll** (formerly \"Pull\"): Daemon makes outbound connections to the server.\n The daemon registers itself and polls for work. Best for daemons behind NAT/firewall.\n\n- **ServerPoll** (formerly \"Push\"): Server makes connections to the daemon.\n Server polls daemon for status and discovery results. Best for DMZ deployments\n where daemon cannot make outbound connections.", "enum": [ "server_poll", "daemon_poll" ] }, "DaemonOrderField": { "type": "string", "description": "Fields that daemons can be ordered/grouped by.", "enum": [ "created_at", "name", "last_seen", "updated_at", "network_id" ] }, "DaemonPromptAction": { "type": "string", "description": "Which daemon-prompt CTA the user chose.", "enum": [ "dismissed", "accepted" ] }, "DaemonPromptResponseRequest": { "type": "object", "description": "Request recording the user's response to the \"Start Discovering Your Network\" prompt.", "required": [ "action" ], "properties": { "action": { "$ref": "#/components/schemas/DaemonPromptAction" } } }, "DaemonRegistrationRequest": { "type": "object", "description": "Daemon registration request from daemon to server", "required": [ "daemon_id", "network_id", "name", "mode", "capabilities" ], "properties": { "capabilities": { "$ref": "#/components/schemas/DaemonCapabilities" }, "daemon_id": { "type": "string", "format": "uuid" }, "integration_targets": { "type": "array", "items": { "$ref": "#/components/schemas/IntegrationTarget" }, "description": "Per-daemon integration targeting from the init command (credentialed cred↔IP and\ncredential-less local sockets). Written to this daemon's Discovery at registration so\nit's present before the first session dispatches. Registration assumes new-daemon →\nnew-server, so there is no legacy bare-`credential_ids` field — bare-uuid env back-compat\nis handled in the daemon's env parser, never on the wire." }, "mode": { "$ref": "#/components/schemas/DaemonMode" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "url": { "type": [ "string", "null" ], "description": "URL is ignored by server - kept for backwards compat with old daemons.\nURL is only set via admin provisioning for ServerPoll daemons." }, "user_id": { "type": "string", "format": "uuid", "description": "User responsible for maintaining this daemon (from frontend install command)\nOptional for backwards compat with old daemons - defaults to nil UUID" }, "version": { "type": [ "string", "null" ], "description": "Daemon software version (optional for backwards compat with old daemons)" } } }, "DaemonRegistrationResponse": { "type": "object", "description": "Daemon registration response from server to daemon", "required": [ "daemon", "host_id" ], "properties": { "daemon": { "$ref": "#/components/schemas/Daemon" }, "host_id": { "type": "string", "format": "uuid" }, "server_capabilities": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServerCapabilities", "description": "Server capabilities (returned if daemon sends version info)" } ] } } }, "DaemonResponse": { "allOf": [ { "$ref": "#/components/schemas/DaemonBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at", "version_status" ], "properties": { "created_at": { "type": "string", "format": "date-time" }, "id": { "type": "string", "format": "uuid" }, "updated_at": { "type": "string", "format": "date-time" }, "version_status": { "$ref": "#/components/schemas/DaemonVersionStatus", "description": "Computed version status including health and warnings" } } } ], "description": "Daemon response for UI including computed version status" }, "DaemonStartupRequest": { "type": "object", "description": "Sent by daemon on startup to report version", "required": [ "daemon_version" ], "properties": { "daemon_version": { "type": "string", "description": "Daemon software version (semver format)" } } }, "DaemonStatus": { "type": "object", "description": "Lightweight daemon status for polling responses.", "required": [ "name", "mode" ], "properties": { "capabilities": { "$ref": "#/components/schemas/DaemonCapabilities", "description": "Backwards compat: pre-v0.15.0 daemons send capabilities instead of interfaced_subnets." }, "interfaced_subnets": { "type": "array", "items": { "$ref": "#/components/schemas/Subnet" }, "description": "Subnets detected from daemon's network ip_addresses. Server resolves these\nvia SubnetService::create (create-or-match by CIDR) to get real IDs.\nv0.15.0+ daemons populate this; pre-v0.15.0 daemons leave it empty." }, "mode": { "$ref": "#/components/schemas/DaemonMode" }, "name": { "type": "string" }, "ready_for_work": { "type": "boolean", "description": "Whether the daemon can accept a new discovery session.\nBoth DaemonPoll and ServerPoll use this to avoid dispatching work to a busy daemon." }, "url": { "type": [ "string", "null" ], "description": "URL is not used by server - kept for backwards compat.\nServer never updates daemon URL from status (URL is set during provisioning)." }, "version": { "type": [ "string", "null" ], "description": "Daemon software version (semver format)" } } }, "DaemonVersionStatus": { "type": "object", "description": "Daemon version status including health and any warnings", "required": [ "status" ], "properties": { "has_correct_docker_volume_mount": { "type": "boolean" }, "status": { "$ref": "#/components/schemas/VersionHealthStatus" }, "supports_unified_discovery": { "type": "boolean" }, "version": { "type": [ "string", "null" ] }, "warnings": { "type": "array", "items": { "$ref": "#/components/schemas/DeprecationWarning" } } } }, "DashboardSummary": { "type": "object", "description": "Dashboard summary response", "required": [ "networks", "daemons", "recent_discoveries", "plan_usage" ], "properties": { "daemons": { "type": "array", "items": { "$ref": "#/components/schemas/DaemonResponse" } }, "networks": { "type": "array", "items": { "$ref": "#/components/schemas/NetworkSummary" } }, "plan_usage": { "$ref": "#/components/schemas/PlanUsage" }, "recent_discoveries": { "type": "array", "items": { "$ref": "#/components/schemas/Discovery" } } } }, "Dependency": { "allOf": [ { "$ref": "#/components/schemas/DependencyBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "color": "Blue", "created_at": "2026-01-15T10:30:00Z", "dependency_type": "RequestPath", "description": "HTTP/HTTPS services dependency", "edge_style": "Bezier", "id": "550e8400-e29b-41d4-a716-446655440008", "lineage_id": null, "members": { "service_ids": [], "type": "Services" }, "name": "Web Services", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "DependencyBase": { "type": "object", "required": [ "name", "network_id", "dependency_type", "members", "color", "edge_style", "tags" ], "properties": { "color": { "$ref": "#/components/schemas/Color" }, "dependency_type": { "$ref": "#/components/schemas/DependencyType" }, "description": { "type": [ "string", "null" ] }, "edge_style": { "$ref": "#/components/schemas/EdgeStyle" }, "members": { "$ref": "#/components/schemas/DependencyMembers", "description": "Members of this dependency: either service IDs or binding IDs." }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/EntitySource", "description": "Will be automatically set to Manual for creation through API" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "DependencyMembers": { "oneOf": [ { "type": "object", "description": "Application-level only. Ordered list of service IDs.", "required": [ "service_ids", "type" ], "properties": { "service_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "type": { "type": "string", "enum": [ "Services" ] } } }, { "type": "object", "description": "Full L3 detail. Ordered list of binding IDs (one per service in the chain).", "required": [ "binding_ids", "type" ], "properties": { "binding_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "type": { "type": "string", "enum": [ "Bindings" ] } } } ], "description": "The members of a dependency: either service-level or binding-level.\nBindings are all-or-nothing: either every position has a binding (full L3 detail)\nor none do (Application-level only)." }, "DependencyOrderField": { "type": "string", "description": "Fields that dependencies can be ordered/grouped by.", "enum": [ "created_at", "name", "dependency_type", "updated_at", "network_id" ] }, "DependencyType": { "type": "string", "enum": [ "RequestPath", "HubAndSpoke" ] }, "DeploymentType": { "type": "string", "enum": [ "cloud", "commercial", "community" ] }, "DeprecationSeverity": { "type": "string", "description": "Severity level for deprecation warnings", "enum": [ "Info", "Warning", "Critical" ] }, "DeprecationWarning": { "type": "object", "description": "Deprecation warning for daemon version", "required": [ "message", "severity" ], "properties": { "message": { "type": "string" }, "severity": { "$ref": "#/components/schemas/DeprecationSeverity" }, "sunset_date": { "type": [ "string", "null" ] } } }, "Discovery": { "allOf": [ { "$ref": "#/components/schemas/DiscoveryBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at", "integration_targets" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "force_full_scan": { "type": "boolean", "description": "When true, the next scan will be a full port scan regardless of interval" }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "integration_targets": { "type": "array", "items": { "$ref": "#/components/schemas/IntegrationTarget" }, "description": "Per-daemon integration targeting: which integrations (credentialed or credential-less\nlocal) run on this daemon, and on which IPs. Delivered via the init command at\nregistration and editable via the discovery modal. Persistent — re-applied every scan.\nThis is the single home for cred↔IP targeting; it replaces the global\n`credential.target_ips` (race-prone, consumed once) and the discovery modal's old\none-shot `pending_credential_ids`." }, "scan_count": { "type": "integer", "format": "int32", "description": "Number of completed scans (incremented by server on session completion)", "readOnly": true, "minimum": 0 }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "DiscoveryBase": { "type": "object", "required": [ "discovery_type", "run_type", "name", "daemon_id", "network_id", "tags" ], "properties": { "daemon_id": { "type": "string", "format": "uuid" }, "discovery_type": { "$ref": "#/components/schemas/DiscoveryType" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "run_type": { "$ref": "#/components/schemas/RunType" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "DiscoveryHostRequest": { "type": "object", "description": "Request type for daemon discovery - accepts full entities with IDs.\nUsed internally by daemons for host creation/upsert, NOT the external API.\nThis supports the discovery workflow where daemons manage entity IDs.\n\n## Backwards compatibility (daemons < v0.16.0)\n\nPre-v0.16.0 daemons send the old field layout:\n - `interfaces` → IPAddress data (now `ip_addresses`)\n - `if_entries` → SNMP Interface data (now `interfaces`)\n\nThe custom deserializer detects the old layout (missing `ip_addresses` field)\nand remaps fields automatically. This can be removed once all daemons are ≥ v0.16.0.", "required": [ "host", "ip_addresses", "ports", "services" ], "properties": { "host": { "$ref": "#/components/schemas/Host" }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" }, "description": "SNMP interface entries (ifTable data) - optional, populated when SNMP is enabled." }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } }, "subnets": { "type": "array", "items": { "$ref": "#/components/schemas/Subnet" }, "description": "Integration-derived subnets (e.g., Docker bridge networks) — created during\ncreate_with_children after service dedup so virtualization.service_id is correct." } } }, "DiscoveryPhase": { "type": "string", "enum": [ "AwaitingSnapshot", "Queued", "Pending", "Starting", "Started", "Scanning", "Complete", "Failed", "Cancelled" ] }, "DiscoveryProtocol": { "type": "string", "description": "Protocol that discovered the physical link between network devices", "enum": [ "LLDP", "CDP" ] }, "DiscoveryType": { "oneOf": [ { "type": "object", "title": "SelfReport", "required": [ "host_id", "type" ], "properties": { "host_id": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "SelfReport" ] } } }, { "type": "object", "title": "Network", "required": [ "subnet_ids", "host_naming_fallback", "type" ], "properties": { "host_naming_fallback": { "$ref": "#/components/schemas/HostNamingFallback" }, "snmp_credentials": { "type": "object", "description": "SNMP credentials for querying devices during discovery\nServer builds this mapping before initiating discovery" }, "subnet_ids": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" } }, "type": { "type": "string", "enum": [ "Network" ] } } }, { "type": "object", "title": "Docker", "required": [ "host_id", "host_naming_fallback", "type" ], "properties": { "host_id": { "type": "string", "format": "uuid" }, "host_naming_fallback": { "$ref": "#/components/schemas/HostNamingFallback" }, "type": { "type": "string", "enum": [ "Docker" ] } } }, { "type": "object", "title": "Unified", "required": [ "host_id", "subnet_ids", "host_naming_fallback", "type" ], "properties": { "host_id": { "type": "string", "format": "uuid", "description": "ID of the host that the daemon is running on" }, "host_naming_fallback": { "$ref": "#/components/schemas/HostNamingFallback", "description": "Fallback strategy for naming discovered hosts" }, "scan_settings": { "$ref": "#/components/schemas/ScanSettings", "description": "Per-discovery scan performance settings" }, "subnet_ids": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" }, "description": "Subnets to scan. None = scan all interfaced subnets." }, "type": { "type": "string", "enum": [ "Unified" ] } } } ] }, "DiscoveryUpdatePayload": { "type": "object", "description": "Progress update from daemon to server during discovery", "required": [ "session_id", "daemon_id", "network_id", "phase", "discovery_type", "progress" ], "properties": { "daemon_id": { "type": "string", "format": "uuid" }, "discovery_id": { "type": [ "string", "null" ], "format": "uuid", "description": "The discovery configuration this session belongs to.\nAlways enriched server-side; daemons do not send this field." }, "discovery_type": { "$ref": "#/components/schemas/DiscoveryType" }, "error": { "type": [ "string", "null" ] }, "estimated_remaining_secs": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "finished_at": { "type": [ "string", "null" ], "format": "date-time" }, "hosts_discovered": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "network_id": { "type": "string", "format": "uuid" }, "phase": { "$ref": "#/components/schemas/DiscoveryPhase" }, "progress": { "type": "integer", "format": "int32", "minimum": 0 }, "scanned": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ScannedEntityIds", "description": "Canonical IDs of entities scanned in this session, populated daemon-\nside at terminal. **Transient**: stripped at `SqlValue::RunType` bind\ntime so it doesn't persist into the historical Discovery row's JSONB.\nAvailable in-memory through the `EntityOperation::Created` event\npublished for the historical Discovery row (the event scope carries\n`Entity::Discovery(...)`, the full in-memory struct), where per-entity\nFK-update subscribers consume it.\n\n`Some(...)` when the daemon is sending the terminal payload over the\nwire. `None` when read back from a persisted historical row, or when\nnot yet set." } ] }, "session_id": { "type": "string", "format": "uuid" }, "started_at": { "type": [ "string", "null" ], "format": "date-time" } } }, "DockerSubnetVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "service_id": { "type": "string", "format": "uuid", "description": "The Docker daemon service that owns this bridge network.\nDifferent Docker daemons on different hosts = distinct bridge subnets." } } }, "DockerVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "compose_project": { "type": [ "string", "null" ] }, "container_id": { "type": [ "string", "null" ] }, "container_name": { "type": [ "string", "null" ] }, "service_id": { "type": "string", "format": "uuid" } } }, "Edge": { "allOf": [ { "$ref": "#/components/schemas/EdgeType" }, { "type": "object", "required": [ "id", "source", "target", "label", "source_handle", "target_handle", "is_multi_hop" ], "properties": { "id": { "type": "string", "format": "uuid" }, "is_multi_hop": { "type": "boolean" }, "label": { "type": [ "string", "null" ] }, "source": { "type": "string", "format": "uuid" }, "source_handle": { "$ref": "#/components/schemas/EdgeHandle" }, "target": { "type": "string", "format": "uuid" }, "target_handle": { "$ref": "#/components/schemas/EdgeHandle" }, "view_config": { "$ref": "#/components/schemas/EdgeViewConfig" } } } ] }, "EdgeDefaultVisibility": { "type": "string", "description": "Whether an edge is visible by default or hidden behind a toggle", "enum": [ "visible", "hidden" ] }, "EdgeHandle": { "type": "string", "enum": [ "Top", "Bottom", "Left", "Right" ] }, "EdgeHighlightBehavior": { "type": "string", "description": "Controls when an edge contributes to node highlighting on selection", "enum": [ "when_visible", "always", "never" ] }, "EdgeStroke": { "type": "string", "description": "Visual stroke style for an edge", "enum": [ "solid", "dashed" ] }, "EdgeStyle": { "type": "string", "enum": [ "Straight", "SmoothStep", "Bezier" ] }, "EdgeType": { "oneOf": [ { "type": "object", "required": [ "host_id", "edge_type" ], "properties": { "edge_type": { "type": "string", "enum": [ "SameHost" ] }, "host_id": { "type": "string", "format": "uuid" } } }, { "type": "object", "required": [ "hypervisor_service_id", "edge_type" ], "properties": { "edge_type": { "type": "string", "enum": [ "Hypervisor" ] }, "hypervisor_service_id": { "type": "string", "format": "uuid" } } }, { "type": "object", "required": [ "host_id", "service_id", "edge_type" ], "properties": { "edge_type": { "type": "string", "enum": [ "ContainerRuntime" ] }, "host_id": { "type": "string", "format": "uuid" }, "service_id": { "type": "string", "format": "uuid" } } }, { "type": "object", "required": [ "dependency_id", "source_id", "target_id", "edge_type" ], "properties": { "dependency_id": { "type": "string", "format": "uuid" }, "edge_type": { "type": "string", "enum": [ "RequestPath" ] }, "source_id": { "type": "string", "format": "uuid" }, "target_id": { "type": "string", "format": "uuid" } } }, { "type": "object", "required": [ "dependency_id", "source_id", "target_id", "edge_type" ], "properties": { "dependency_id": { "type": "string", "format": "uuid" }, "edge_type": { "type": "string", "enum": [ "HubAndSpoke" ] }, "source_id": { "type": "string", "format": "uuid" }, "target_id": { "type": "string", "format": "uuid" } } }, { "type": "object", "description": "Physical link discovered via LLDP/CDP neighbor discovery", "required": [ "source_entity_id", "target_entity_id", "protocol", "edge_type" ], "properties": { "edge_type": { "type": "string", "enum": [ "PhysicalLink" ] }, "protocol": { "$ref": "#/components/schemas/DiscoveryProtocol" }, "source_entity_id": { "type": "string", "format": "uuid" }, "target_entity_id": { "type": "string", "format": "uuid" } } } ] }, "EdgeTypeDiscriminants": { "type": "string", "enum": [ "SameHost", "Hypervisor", "ContainerRuntime", "RequestPath", "HubAndSpoke", "PhysicalLink" ] }, "EdgeViewConfig": { "oneOf": [ { "type": "object", "description": "Edge is not available in this view", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "disabled" ] } } }, { "type": "object", "description": "Edge is active in this view with specific properties", "required": [ "affects_layout", "default_visibility", "stroke", "highlight_behavior", "will_target_container", "show_directionality", "type" ], "properties": { "affects_layout": { "type": "boolean", "description": "Whether ELK should use this edge for layout positioning" }, "default_visibility": { "$ref": "#/components/schemas/EdgeDefaultVisibility", "description": "Whether the edge is shown by default or hidden behind a toggle" }, "highlight_behavior": { "$ref": "#/components/schemas/EdgeHighlightBehavior", "description": "When this edge contributes to node highlighting on selection" }, "show_directionality": { "type": "boolean", "description": "Whether this edge should show directional animation when highlighted" }, "stroke": { "$ref": "#/components/schemas/EdgeStroke", "description": "Visual stroke style" }, "type": { "type": "string", "enum": [ "active" ] }, "will_target_container": { "type": "boolean", "description": "Whether this edge should be elevated to target an accepting container\ninstead of the element inside it" } } } ], "description": "Per-view configuration for an edge: disabled (not in this view) or active with properties" }, "ElementEntityType": { "oneOf": [ { "type": "object", "required": [ "subnet_id", "element_type" ], "properties": { "element_type": { "type": "string", "enum": [ "IPAddress" ] }, "ip_address_id": { "type": [ "string", "null" ], "format": "uuid" }, "subnet_id": { "type": "string", "format": "uuid" } } }, { "type": "object", "required": [ "element_type" ], "properties": { "element_type": { "type": "string", "enum": [ "Service" ] } } }, { "type": "object", "required": [ "element_type" ], "properties": { "element_type": { "type": "string", "enum": [ "Host" ] } } }, { "type": "object", "required": [ "interface_id", "element_type" ], "properties": { "element_type": { "type": "string", "enum": [ "Interface" ] }, "interface_id": { "type": "string", "format": "uuid" } } } ] }, "EmailInstallCommandRequest": { "type": "object", "description": "Request body for emailing an install command to the authenticated user.", "required": [ "install_command", "os" ], "properties": { "install_command": { "type": "string" }, "os": { "type": "string" } } }, "EmailSettings": { "type": "object", "description": "Per-user toggles for the user-pausable email categories. Each field maps\n1:1 to a [`PausableCategory`]; required emails are never gated here.\n\nStored as a JSONB blob, so new categories are added as new fields rather\nthan via migration. New fields carry `#[serde(default = \"default_true\")]`\nso a category is opted in by default if its key is absent from the stored\nJSON.", "required": [ "discovery_digest" ], "properties": { "daemon_alerts": { "type": "boolean" }, "discovery_digest": { "type": "boolean" }, "product_onboarding": { "type": "boolean" }, "trial_and_usage": { "type": "boolean" } } }, "EnterpriseInquiryRequest": { "type": "object", "description": "Enterprise plan inquiry request", "required": [ "email", "name", "company", "team_size", "message" ], "properties": { "company": { "type": "string", "description": "Company name" }, "email": { "type": "string", "description": "Contact email" }, "message": { "type": "string", "description": "Message/use case description" }, "name": { "type": "string", "description": "Contact name" }, "network_count": { "type": [ "integer", "null" ], "format": "int64", "description": "Number of networks/sites" }, "plan_type": { "type": [ "string", "null" ], "description": "Plan type being inquired about" }, "team_size": { "type": "string", "description": "Team/company size: 1-10, 11-25, 26-50, 51-100, 101-250, 251-500, 501-1000, 1001+" }, "urgency": { "type": [ "string", "null" ], "description": "Urgency: immediately, 1-3 months, 3-6 months, exploring" } } }, "EntityDiscriminants": { "type": "string", "enum": [ "Organization", "Invite", "Share", "Network", "DaemonApiKey", "UserApiKey", "User", "Tag", "Discovery", "Daemon", "Host", "Service", "Port", "Binding", "IPAddress", "Interface", "Credential", "Subnet", "Vlan", "Dependency", "Topology", "Snapshot", "Unknown" ] }, "EntitySource": { "oneOf": [ { "type": "object", "title": "Manual", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Manual" ] } } }, { "type": "object", "title": "System", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "System" ] } } }, { "type": "object", "title": "Discovery", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Discovery" ] } } }, { "type": "object", "title": "DiscoveryWithMatch", "required": [ "details", "type" ], "properties": { "details": { "$ref": "#/components/schemas/MatchDetails" }, "type": { "type": "string", "enum": [ "DiscoveryWithMatch" ] } } }, { "type": "object", "title": "Unknown", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Unknown" ] } } } ] }, "EsxiVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "service_id": { "type": "string", "format": "uuid" }, "vm_id": { "type": [ "string", "null" ] }, "vm_name": { "type": [ "string", "null" ] } } }, "FileOrInline": { "oneOf": [ { "type": "object", "required": [ "value", "mode" ], "properties": { "mode": { "type": "string", "enum": [ "Inline" ] }, "value": { "type": "string" } } }, { "type": "object", "required": [ "path", "mode" ], "properties": { "mode": { "type": "string", "enum": [ "FilePath" ] }, "path": { "type": "string" } } } ], "description": "Non-secret value that can be inline content or a file path on daemon host." }, "FinalizePaymentMethodRequest": { "type": "object", "description": "Request to finalize a client-confirmed SetupIntent (set the collected card\nas the customer's default payment method).", "required": [ "setup_intent_id" ], "properties": { "setup_intent_id": { "type": "string" } } }, "ForgotPasswordRequest": { "type": "object", "required": [ "email" ], "properties": { "email": { "type": "string", "format": "email" } } }, "Host": { "allOf": [ { "$ref": "#/components/schemas/HostBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Discovery (historical row) that first observed this entity. Set once\n(post-terminal); immutable thereafter via the `IS NULL` guard in\n`update_discovery_fks`.", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Discovery (historical row) that last touched this entity. Populated\npost-terminal by the per-entity-service subscriber on\n`DiscoveryProcessed`. NULL until the first successful discovery\nsession terminates after this row was created.", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "description": "Last successful natural-key match by daemon discovery against this\nlive row. Refreshed every scan, regardless of field changes.", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Lineage pointer on closed historical rows back to the live row whose\nstate they capture. NULL on live rows.", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "description": "SCD2: when this row version became live. Equal to `created_at` for\nrows that have never ridden a snapshot; advanced to the snapshot's\n`taken_at` for live rows after a network snapshot fires.", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "description": "SCD2: when this row was closed by a snapshot. NULL = currently live.", "readOnly": true } } } ], "example": { "created_at": "2026-01-15T10:30:00Z", "credential_assignments": [], "description": "Primary web server", "first_discovery_id": null, "hidden": false, "hostname": "web-server-01.local", "id": "550e8400-e29b-41d4-a716-446655440003", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "web-server-01", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } }, "HostBase": { "type": "object", "description": "Base data for a Host entity (stored in database).\nChild entities (ip_addresses, ports, services) are stored in their own tables\nand queried by `host_id`. They are NOT stored on the host.", "required": [ "name", "network_id", "hostname", "description", "source", "virtualization", "hidden", "tags", "credential_assignments" ], "properties": { "chassis_id": { "type": [ "string", "null" ], "description": "LLDP lldpLocChassisId - globally unique device identifier for deduplication" }, "credential_assignments": { "type": "array", "items": { "$ref": "#/components/schemas/CredentialAssignment" }, "description": "Credential assignments for this host (hydrated from junction table)." }, "description": { "type": [ "string", "null" ] }, "hidden": { "type": "boolean" }, "hostname": { "type": [ "string", "null" ] }, "management_url": { "type": [ "string", "null" ], "description": "URL for device management interface (manual or discovered)" }, "manufacturer": { "type": [ "string", "null" ], "description": "ENTITY-MIB entPhysicalMfgName - hardware manufacturer" }, "model": { "type": [ "string", "null" ], "description": "ENTITY-MIB entPhysicalModelName - hardware model" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "serial_number": { "type": [ "string", "null" ], "description": "ENTITY-MIB entPhysicalSerialNum - hardware serial number" }, "source": { "$ref": "#/components/schemas/EntitySource" }, "sys_contact": { "type": [ "string", "null" ], "description": "SNMP sysContact.0 - admin contact info" }, "sys_descr": { "type": [ "string", "null" ], "description": "SNMP sysDescr.0 - full system description" }, "sys_location": { "type": [ "string", "null" ], "description": "SNMP sysLocation.0 - physical location" }, "sys_name": { "type": [ "string", "null" ], "description": "SNMP sysName.0 - administratively-assigned hostname" }, "sys_object_id": { "type": [ "string", "null" ], "description": "SNMP sysObjectID.0 - vendor OID for device identification" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostVirtualization" } ] } } }, "HostNamingFallback": { "type": "string", "enum": [ "Ip", "BestService" ] }, "HostOrderField": { "type": "string", "description": "Fields that hosts can be ordered/grouped by.", "enum": [ "created_at", "name", "hostname", "updated_at", "virtualized_by", "network_id", "interface_ip" ] }, "HostResponse": { "type": "object", "description": "Response type for host endpoints.\nIncludes children (ip_addresses, ports, services, interfaces).", "required": [ "id", "created_at", "updated_at", "name", "network_id", "source", "hidden", "tags", "ip_addresses", "ports", "services", "interfaces" ], "properties": { "chassis_id": { "type": [ "string", "null" ] }, "created_at": { "type": "string", "format": "date-time" }, "credential_assignments": { "type": "array", "items": { "$ref": "#/components/schemas/CredentialAssignment" } }, "description": { "type": [ "string", "null" ] }, "hidden": { "type": "boolean" }, "hostname": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" }, "description": "SNMP ifTable entries" }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "management_url": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } }, "source": { "$ref": "#/components/schemas/EntitySource" }, "sys_contact": { "type": [ "string", "null" ] }, "sys_descr": { "type": [ "string", "null" ] }, "sys_location": { "type": [ "string", "null" ] }, "sys_object_id": { "type": [ "string", "null" ] }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "updated_at": { "type": "string", "format": "date-time" }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostVirtualization" } ] } }, "example": { "created_at": "2026-01-15T10:30:00Z", "credential_assignments": [], "description": "Primary web server", "hidden": false, "hostname": "web-server-01.local", "id": "550e8400-e29b-41d4-a716-446655440003", "interfaces": [ { "admin_status": "Up", "cdp_address": null, "cdp_device_id": null, "cdp_platform": null, "cdp_port_id": null, "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-44665544000f", "if_alias": "Uplink to Core Switch", "if_descr": "GigabitEthernet0/1", "if_index": 1, "if_name": "Gi0/1", "if_type": 6, "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "lldp_chassis_id": null, "lldp_mgmt_addr": null, "lldp_port_desc": null, "lldp_port_id": null, "lldp_sys_desc": null, "lldp_sys_name": null, "mac_address": "DE:AD:BE:EF:CA:FE", "neighbor": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "oper_status": "Up", "speed_bps": 1000000000, "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "ip_addresses": [ { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440005", "ip_address": "192.168.1.100", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "mac_address": "DE:AD:BE:EF:CA:FE", "name": "eth0", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "subnet_id": "550e8400-e29b-41d4-a716-446655440004", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "name": "web-server-01", "network_id": "550e8400-e29b-41d4-a716-446655440002", "ports": [ { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440006", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "number": 80, "protocol": "Tcp", "type": "Http", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "services": [ { "bindings": [ { "created_at": "2026-06-29T15:15:28.324972Z", "first_discovery_id": null, "id": "4fb8a65a-bb7e-40ed-8836-0c36b12a1149", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.324972Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.324972Z", "valid_from": "2026-06-29T15:15:28.324972Z", "valid_to": null } ], "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440007", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "nginx", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "service_definition": "Frigate", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } ], "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "virtualization": null } }, "HostVirtualization": { "oneOf": [ { "type": "object", "title": "Proxmox", "required": [ "details", "type" ], "properties": { "details": { "$ref": "#/components/schemas/ProxmoxVirtualization" }, "type": { "type": "string", "enum": [ "Proxmox" ] } } }, { "type": "object", "title": "VCenter", "required": [ "details", "type" ], "properties": { "details": { "$ref": "#/components/schemas/VCenterVirtualization" }, "type": { "type": "string", "enum": [ "VCenter" ] } } }, { "type": "object", "title": "ESXi", "required": [ "details", "type" ], "properties": { "details": { "$ref": "#/components/schemas/EsxiVirtualization" }, "type": { "type": "string", "enum": [ "ESXi" ] } } } ], "title": "HostVirtualization" }, "IPAddress": { "allOf": [ { "$ref": "#/components/schemas/IPAddressBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440005", "ip_address": "192.168.1.100", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "mac_address": "DE:AD:BE:EF:CA:FE", "name": "eth0", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "subnet_id": "550e8400-e29b-41d4-a716-446655440004", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "IPAddressBase": { "type": "object", "required": [ "network_id", "host_id", "subnet_id", "ip_address", "name" ], "properties": { "host_id": { "type": "string", "format": "uuid" }, "ip_address": { "type": "string" }, "mac_address": { "type": [ "string", "null" ], "description": "MAC address discovered from ARP, SNMP, or Docker - immutable once set" }, "name": { "type": [ "string", "null" ] }, "network_id": { "type": "string", "format": "uuid" }, "position": { "type": "integer", "format": "int32", "description": "Position of this IP address in the host's IP address list (for ordering)" }, "subnet_id": { "type": "string", "format": "uuid" } } }, "IPAddressInput": { "type": "object", "description": "Input for creating or updating an interface.\nUsed in both CreateHostRequest and UpdateHostRequest.\nClient must provide a UUID for the interface.", "required": [ "id", "subnet_id", "ip_address" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Client-provided UUID for this interface" }, "ip_address": { "type": "string" }, "mac_address": { "type": [ "string", "null" ] }, "name": { "type": [ "string", "null" ] }, "position": { "type": [ "integer", "null" ], "format": "int32", "description": "Position in the host's interface list (for ordering).\nIf omitted on create: appends to end of list.\nIf omitted on update: existing ip_addresses keep their positions; new ip_addresses append.\nMust be all specified or all omitted across all ip_addresses in the request." }, "subnet_id": { "type": "string", "format": "uuid" } } }, "IdentifiedRule_ContainerRule": { "type": "object", "description": "Generic wrapper that gives any rule type a stable UUID identity.", "required": [ "id", "rule" ], "properties": { "id": { "type": "string", "format": "uuid" }, "rule": { "oneOf": [ { "type": "string", "enum": [ "BySubnet" ] }, { "type": "string", "enum": [ "MergeDockerBridges" ] }, { "type": "object", "required": [ "ByApplication" ], "properties": { "ByApplication": { "type": "object", "properties": { "tag_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } } } } } }, { "type": "string", "enum": [ "ByHost" ] } ], "description": "Rules that change which containers exist and how they nest.\nContainer titles are data-driven (subnet CIDR, host names), not user-configurable." } } }, "IdentifiedRule_ElementRule": { "type": "object", "description": "Generic wrapper that gives any rule type a stable UUID identity.", "required": [ "id", "rule" ], "properties": { "id": { "type": "string", "format": "uuid" }, "rule": { "oneOf": [ { "type": "object", "required": [ "ByServiceCategory" ], "properties": { "ByServiceCategory": { "type": "object", "required": [ "categories" ], "properties": { "categories": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceCategory" } }, "is_infra_rule": { "type": "boolean", "description": "Set by the backend on the default infrastructure rule.\nFrontend uses this to identify the infra container for auto-collapse.", "readOnly": true }, "title": { "type": [ "string", "null" ] } } } } }, { "type": "object", "required": [ "ByTag" ], "properties": { "ByTag": { "type": "object", "required": [ "tag_ids" ], "properties": { "tag_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "title": { "type": [ "string", "null" ] } } } } }, { "type": "string", "enum": [ "ByHypervisor" ] }, { "type": "string", "enum": [ "ByContainerRuntime" ] }, { "type": "string", "enum": [ "ByStack" ] }, { "type": "string", "description": "Groups trunk ports (ports with tagged VLANs) into a \"Trunk Ports\" subcontainer.\nHigher priority than ByVLAN — prevents trunk ports from being grouped by VLAN.", "enum": [ "ByTrunkPort" ] }, { "type": "string", "description": "Groups access ports by their native VLAN ID into per-VLAN subcontainers.", "enum": [ "ByVLAN" ] }, { "type": "string", "description": "Groups ports by operational status (Up, Down, etc.) into per-status subcontainers.", "enum": [ "ByPortOpStatus" ] } ], "description": "Rules that organize nodes within a container into sub-groups." } } }, "IfAdminStatus": { "type": "string", "description": "SNMP ifAdminStatus values per IF-MIB RFC 2863", "enum": [ "Up", "Down", "Testing" ] }, "IfOperStatus": { "type": "string", "description": "SNMP ifOperStatus values per IF-MIB RFC 2863", "enum": [ "Up", "Down", "Testing", "Unknown", "Dormant", "NotPresent", "LowerLayerDown" ] }, "InlineGroup": { "type": "object", "description": "Visual grouping metadata for inlined entities.\nEntities sharing the same `group_id` are rendered together in the element card.", "required": [ "entity_id", "group_id", "role" ], "properties": { "entity_id": { "type": "string", "format": "uuid", "description": "The inlined entity's ID (e.g., service ID)." }, "group_id": { "type": "string", "format": "uuid", "description": "Shared by all members of the visual group." }, "role": { "$ref": "#/components/schemas/InlineGroupRole" } } }, "InlineGroupRole": { "type": "string", "description": "Role of an inlined entity within its visual group.", "enum": [ "Header", "Member" ] }, "IntegrationTarget": { "oneOf": [ { "type": "object", "description": "The daemon's own host — realized as a 127.0.0.1 IP-override (e.g. a local Docker/Podman\nsocket, or any credential the user pins to the daemon host without naming its IP).", "required": [ "credential_id", "scope" ], "properties": { "credential_id": { "type": "string", "format": "uuid" }, "scope": { "type": "string", "enum": [ "DaemonHost" ] } } }, { "type": "object", "description": "All hosts on the network — a broadcast default credential.", "required": [ "credential_id", "scope" ], "properties": { "credential_id": { "type": "string", "format": "uuid" }, "scope": { "type": "string", "enum": [ "Network" ] } } }, { "type": "object", "description": "Specific host IPs — one IP-override per address.", "required": [ "credential_id", "ips", "scope" ], "properties": { "credential_id": { "type": "string", "format": "uuid" }, "ips": { "type": "array", "items": { "type": "string" } }, "scope": { "type": "string", "enum": [ "Hosts" ] } } } ], "description": "Per-daemon integration targeting, stored on the `Discovery` entity and delivered via the\ninit command at registration. Each entry references exactly one stored credential and says\nwhere it applies on this daemon. This is the single home for cred↔IP targeting — it replaces\nthe global, race-prone `credential.target_ips`.\n\nThe variants ARE the scopes; their strum [`Target`] discriminants are the capability enum that\n`CredentialType::targets()` returns and validates against (single source of truth). Every\ntarget carries a real `credential_id` — there is no credential-less branch and no nil\nsentinel; a local socket is just a credential whose type targets only the daemon host." }, "Interface": { "allOf": [ { "$ref": "#/components/schemas/InterfaceBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ] }, "InterfaceBase": { "type": "object", "required": [ "host_id", "network_id", "if_index", "if_descr", "if_type", "admin_status", "oper_status" ], "properties": { "admin_status": { "$ref": "#/components/schemas/IfAdminStatus", "description": "SNMP ifAdminStatus: 1=up, 2=down, 3=testing" }, "cdp_address": { "type": [ "string", "null" ], "description": "Remote management IP from CDP (cdpCacheAddress)" }, "cdp_device_id": { "type": [ "string", "null" ], "description": "Remote device ID from CDP (typically hostname, locally unique)" }, "cdp_platform": { "type": [ "string", "null" ], "description": "Remote platform from CDP (e.g., \"Cisco IOS\")" }, "cdp_port_id": { "type": [ "string", "null" ], "description": "Remote port ID from CDP" }, "fdb_macs": { "type": [ "array", "null" ], "items": { "type": "string" }, "description": "Bridge FDB: learned MAC addresses on this switch port.\nSingle-MAC ports can be resolved to neighbor links server-side.\nMulti-MAC ports indicate uplinks where LLDP/CDP is the better source." }, "host_id": { "type": "string", "format": "uuid" }, "if_alias": { "type": [ "string", "null" ], "description": "SNMP ifAlias - user-configured description" }, "if_descr": { "type": "string", "description": "SNMP ifDescr - interface description (e.g., GigabitEthernet0/1)" }, "if_index": { "type": "integer", "format": "int32", "description": "SNMP ifIndex - stable identifier within device" }, "if_name": { "type": [ "string", "null" ], "description": "SNMP ifName - short interface name (e.g., Gi1/0/1)" }, "if_type": { "type": "integer", "format": "int32", "description": "SNMP ifType - IANAifType integer (6=ethernet, 24=loopback, etc.)" }, "ip_address_id": { "type": [ "string", "null" ], "format": "uuid", "description": "FK to IPAddress entity - this port's IP assignment (must be on same host).\nOld daemons send this as \"interface_id\"." }, "lldp_chassis_id": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/LldpChassisId", "description": "Remote chassis identifier from LLDP neighbor (globally/locally unique)" } ] }, "lldp_mgmt_addr": { "type": [ "string", "null" ], "description": "Remote management IP from LLDP neighbor (lldpRemManAddr)" }, "lldp_port_desc": { "type": [ "string", "null" ], "description": "Remote port description from LLDP neighbor (lldpRemPortDesc)" }, "lldp_port_id": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/LldpPortId", "description": "Remote port identifier from LLDP neighbor" } ] }, "lldp_sys_desc": { "type": [ "string", "null" ], "description": "Remote system description from LLDP neighbor (lldpRemSysDesc) - platform info" }, "lldp_sys_name": { "type": [ "string", "null" ], "description": "Remote system name from LLDP neighbor (lldpRemSysName)" }, "mac_address": { "type": [ "string", "null" ], "description": "MAC address from SNMP ifPhysAddress - immutable once set" }, "native_vlan_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Native/untagged VLAN entity ID on this port (resolved from Q-BRIDGE dot1qPvid)" }, "neighbor": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/Neighbor", "description": "Resolved neighbor connection (mutually exclusive: either Interface or Host)" } ] }, "network_id": { "type": "string", "format": "uuid" }, "oper_status": { "$ref": "#/components/schemas/IfOperStatus", "description": "SNMP ifOperStatus: 1=up, 2=down, 3=testing, 4=unknown, 5=dormant, 6=notPresent, 7=lowerLayerDown" }, "speed_bps": { "type": [ "integer", "null" ], "format": "int64", "description": "Interface speed from ifSpeed/ifHighSpeed in bits per second" }, "vlan_ids": { "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" }, "description": "Tagged VLAN entity IDs on this port (resolved from Q-BRIDGE dot1qVlanCurrentEgressPorts)" } } }, "InterfaceInput": { "type": "object", "description": "Input for creating an SNMP interface entry (ifTable data).\nUsed in CreateHostRequest. Server assigns UUIDs since nothing references\nInterface IDs at creation time (neighbor resolution is done server-side).", "required": [ "if_index", "if_descr" ], "properties": { "admin_status": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/IfAdminStatus", "description": "SNMP ifAdminStatus" } ] }, "if_alias": { "type": [ "string", "null" ], "description": "SNMP ifAlias - user-configured description" }, "if_descr": { "type": "string", "description": "SNMP ifDescr - interface description (e.g., GigabitEthernet0/1)" }, "if_index": { "type": "integer", "format": "int32", "description": "SNMP ifIndex - stable identifier within device" }, "if_type": { "type": [ "integer", "null" ], "format": "int32", "description": "SNMP ifType - IANAifType integer (6=ethernet, 24=loopback, etc.)" }, "ip_address_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Optional FK to Interface - links this SNMP port to its IP assignment" }, "mac_address": { "type": [ "string", "null" ], "description": "MAC address from SNMP ifPhysAddress" }, "oper_status": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/IfOperStatus", "description": "SNMP ifOperStatus" } ] }, "speed_bps": { "type": [ "integer", "null" ], "format": "int64", "description": "Interface speed in bits per second" } } }, "Invite": { "allOf": [ { "$ref": "#/components/schemas/InviteBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "InviteBase": { "type": "object", "required": [ "organization_id", "permissions", "network_ids", "url", "created_by", "expires_at", "send_to" ], "properties": { "created_by": { "type": "string", "format": "uuid" }, "expires_at": { "type": "string", "format": "date-time" }, "network_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "organization_id": { "type": "string", "format": "uuid" }, "permissions": { "$ref": "#/components/schemas/UserOrgPermissions" }, "send_to": { "type": [ "string", "null" ], "description": "Optional email address to send the invite to" }, "url": { "type": "string" } } }, "Ixy": { "type": "object", "required": [ "x", "y" ], "properties": { "x": { "type": "integer" }, "y": { "type": "integer" } } }, "LldpChassisId": { "oneOf": [ { "type": "object", "description": "Subtype 1: Chassis component (e.g., backplane serial number)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "ChassisComponent" ] }, "value": { "type": "string", "description": "Subtype 1: Chassis component (e.g., backplane serial number)" } } }, { "type": "object", "description": "Subtype 2: Interface alias (ifAlias from IF-MIB)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "InterfaceAlias" ] }, "value": { "type": "string", "description": "Subtype 2: Interface alias (ifAlias from IF-MIB)" } } }, { "type": "object", "description": "Subtype 3: Port component (e.g., backplane port number)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "PortComponent" ] }, "value": { "type": "string", "description": "Subtype 3: Port component (e.g., backplane port number)" } } }, { "type": "object", "description": "Subtype 4: MAC address (most common)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "MacAddress" ] }, "value": { "type": "string", "description": "Subtype 4: MAC address (most common)" } } }, { "type": "object", "description": "Subtype 5: Network address (IP address stored as string)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "NetworkAddress" ] }, "value": { "type": "string", "description": "Subtype 5: Network address (IP address stored as string)" } } }, { "type": "object", "description": "Subtype 6: Interface name (ifName from IF-MIB)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "InterfaceName" ] }, "value": { "type": "string", "description": "Subtype 6: Interface name (ifName from IF-MIB)" } } }, { "type": "object", "description": "Subtype 7: Locally assigned (device-specific identifier)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "LocallyAssigned" ] }, "value": { "type": "string", "description": "Subtype 7: Locally assigned (device-specific identifier)" } } } ], "description": "LLDP Chassis ID subtypes per IEEE 802.1AB.\n\nThe chassis ID identifies the remote device. Different network equipment\nmay use different subtypes depending on configuration and capabilities." }, "LldpPortId": { "oneOf": [ { "type": "object", "description": "Subtype 1: Interface alias (ifAlias from IF-MIB)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "InterfaceAlias" ] }, "value": { "type": "string", "description": "Subtype 1: Interface alias (ifAlias from IF-MIB)" } } }, { "type": "object", "description": "Subtype 2: Port component (e.g., backplane port number)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "PortComponent" ] }, "value": { "type": "string", "description": "Subtype 2: Port component (e.g., backplane port number)" } } }, { "type": "object", "description": "Subtype 3: MAC address", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "MacAddress" ] }, "value": { "type": "string", "description": "Subtype 3: MAC address" } } }, { "type": "object", "description": "Subtype 4: Network address (IP address stored as string)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "NetworkAddress" ] }, "value": { "type": "string", "description": "Subtype 4: Network address (IP address stored as string)" } } }, { "type": "object", "description": "Subtype 5: Interface name (ifName from IF-MIB)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "InterfaceName" ] }, "value": { "type": "string", "description": "Subtype 5: Interface name (ifName from IF-MIB)" } } }, { "type": "object", "description": "Subtype 6: Agent circuit ID (used by some providers)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "AgentCircuitId" ] }, "value": { "type": "string", "description": "Subtype 6: Agent circuit ID (used by some providers)" } } }, { "type": "object", "description": "Subtype 7: Locally assigned (device-specific identifier)", "required": [ "value", "subtype" ], "properties": { "subtype": { "type": "string", "enum": [ "LocallyAssigned" ] }, "value": { "type": "string", "description": "Subtype 7: Locally assigned (device-specific identifier)" } } } ], "description": "LLDP Port ID subtypes per IEEE 802.1AB.\n\nThe port ID identifies the specific port on the remote device." }, "LoginRequest": { "type": "object", "description": "Login request from client", "required": [ "email", "password" ], "properties": { "email": { "type": "string", "format": "email" }, "password": { "type": "string" } } }, "MatchConfidence": { "type": "string", "enum": [ "NotApplicable", "Low", "Medium", "High", "Certain" ] }, "MatchDetails": { "type": "object", "required": [ "reason", "confidence" ], "properties": { "confidence": { "$ref": "#/components/schemas/MatchConfidence" }, "reason": { "$ref": "#/components/schemas/MatchReason" } } }, "MatchReason": { "oneOf": [ { "type": "object", "required": [ "type", "data" ], "properties": { "data": { "type": "string" }, "type": { "type": "string", "enum": [ "reason" ] } } }, { "type": "object", "required": [ "type", "data" ], "properties": { "data": { "type": "array", "items": {}, "description": "Tuple of [name: string, children: MatchReason[]]" }, "type": { "type": "string", "enum": [ "container" ] } } } ], "description": "Match reason - either a simple reason string or a container with nested reasons" }, "Neighbor": { "oneOf": [ { "type": "object", "description": "Full resolution - the specific remote port was identified", "required": [ "id", "type" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Full resolution - the specific remote port was identified" }, "type": { "type": "string", "enum": [ "Interface" ] } } }, { "type": "object", "description": "Partial resolution - the remote device was identified but not the specific port", "required": [ "id", "type" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Partial resolution - the remote device was identified but not the specific port" }, "type": { "type": "string", "enum": [ "Host" ] } } } ], "description": "Resolved LLDP/CDP neighbor connection.\n\nRepresents the remote endpoint this port connects to, discovered via LLDP or CDP.\nThe two variants are mutually exclusive and represent different resolution states." }, "Network": { "allOf": [ { "$ref": "#/components/schemas/NetworkBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ], "example": { "created_at": "2026-01-15T10:30:00Z", "credential_ids": [], "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Home Network", "organization_id": "550e8400-e29b-41d4-a716-446655440001", "tags": [], "updated_at": "2026-01-15T10:30:00Z" } }, "NetworkBase": { "type": "object", "required": [ "name", "organization_id", "tags", "credential_ids" ], "properties": { "credential_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Credential IDs associated with this network (hydrated from junction table)." }, "name": { "type": "string" }, "organization_id": { "type": "string", "format": "uuid" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "NetworkSetup": { "type": "object", "description": "Network configuration for setup", "required": [ "name" ], "properties": { "name": { "type": "string" } } }, "NetworkSummary": { "type": "object", "description": "Per-network summary of entity counts", "required": [ "id", "name", "host_count", "service_count", "subnet_count", "daemon_count" ], "properties": { "daemon_count": { "type": "integer", "format": "int64", "minimum": 0 }, "host_count": { "type": "integer", "format": "int64", "minimum": 0 }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "service_count": { "type": "integer", "format": "int64", "minimum": 0 }, "subnet_count": { "type": "integer", "format": "int64", "minimum": 0 } } }, "Node": { "allOf": [ { "$ref": "#/components/schemas/NodeType" }, { "type": "object", "required": [ "id", "position", "size" ], "properties": { "header": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "position": { "$ref": "#/components/schemas/Ixy" }, "size": { "$ref": "#/components/schemas/Uxy" } } } ] }, "NodeType": { "oneOf": [ { "type": "object", "required": [ "node_type" ], "properties": { "associated_service_definition": { "type": [ "string", "null" ], "description": "Service definition ID for logo rendering (e.g. \"Docker\", \"Proxmox VE\").\nUsed by Hypervisor and Stack subcontainers to show the service's logo." }, "color": { "type": [ "string", "null" ], "description": "Display color name (set by graph builder from the source entity, e.g. subnet type)" }, "container_type": { "$ref": "#/components/schemas/ContainerType" }, "element_rule_id": { "type": [ "string", "null" ], "format": "uuid", "description": "ID of the element rule that created this container (for subcontainers like NestedTag, Hypervisor, etc.)" }, "entity_id": { "type": [ "string", "null" ], "format": "uuid", "description": "The entity this container represents (e.g. host ID for Host containers,\nsubnet ID for Subnet containers). Used for ownership mapping on the frontend." }, "icon": { "type": [ "string", "null" ], "description": "Display icon name (set by graph builder from the source entity, e.g. subnet type)" }, "node_type": { "type": "string", "enum": [ "Container" ] }, "parent_container_id": { "type": [ "string", "null" ], "format": "uuid" }, "will_accept_edges": { "type": "boolean", "description": "When true, this container accepts edges with `will_target_container`, causing\nthem to visually attach here instead of at elements inside." } } }, { "allOf": [ { "$ref": "#/components/schemas/ElementEntityType" }, { "type": "object", "required": [ "host_id" ], "properties": { "container_id": { "type": "string", "format": "uuid" }, "host_id": { "type": "string", "format": "uuid" }, "inline_groups": { "type": "array", "items": { "$ref": "#/components/schemas/InlineGroup" }, "description": "Visual grouping metadata for services inlined on this element.\nPopulated by element rules (e.g., Docker containers on a VM host\nget InlineGroups with Header/Member roles for dotted-border rendering)." } } }, { "type": "object", "required": [ "node_type" ], "properties": { "node_type": { "type": "string", "enum": [ "Element" ] } } } ] } ] }, "OidcProviderMetadata": { "type": "object", "required": [ "name", "slug" ], "properties": { "logo": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "slug": { "type": "string" } } }, "OnboardingNetworkState": { "type": "object", "description": "Network data in onboarding state response", "required": [ "name" ], "properties": { "id": { "type": [ "string", "null" ], "format": "uuid", "description": "Network ID (if created)" }, "name": { "type": "string", "description": "Network name" } } }, "OnboardingOperationDiscriminants": { "type": "string", "enum": [ "OrgCreated", "OnboardingModalCompleted", "PlanSelected", "DaemonPromptDismissed", "DaemonPromptAccepted", "FirstDaemonRegistered", "FirstTopologyRebuild", "FirstDiscoveryCompleted", "FirstHostDiscovered", "SecondNetworkCreated", "FirstTagCreated", "FirstDependencyCreated", "FirstUserApiKeyCreated", "FirstSnmpCredentialCreated", "FirstApplicationTagCreated", "FirstCredentialCreated", "FirstSnapshotCreated", "InviteSent", "InviteAccepted", "ProfileCompleted", "ReferralSourceCompleted" ] }, "OnboardingStateResponse": { "type": "object", "description": "Response from onboarding state endpoint", "properties": { "network": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/OnboardingNetworkState", "description": "Network from pending setup (with name and ID)" } ] }, "network_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Network ID from pending setup (if any)" }, "org_name": { "type": [ "string", "null" ], "description": "Organization name from pending setup" }, "step": { "type": [ "string", "null" ], "description": "Current onboarding step (if any)" }, "use_case": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/UseCase", "description": "Use case selection (homelab, company, msp)" } ] } } }, "OnboardingStepRequest": { "type": "object", "description": "Request to save onboarding step", "required": [ "step" ], "properties": { "step": { "type": "string" }, "use_case": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/UseCase", "description": "Use case selection (homelab, company, msp)" } ] } } }, "OrderDirection": { "type": "string", "description": "Direction for ORDER BY clauses.", "enum": [ "asc", "desc" ] }, "Organization": { "allOf": [ { "$ref": "#/components/schemas/OrganizationBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "OrganizationBase": { "type": "object", "required": [ "name", "plan", "plan_status", "onboarding" ], "properties": { "discount_save_offer_active_until": { "type": [ "string", "null" ], "format": "date-time", "description": "When the currently-active save-offer discount window expires. The\nBillingTab chip renders only while `> now()`; expiry needs no\ncleanup job.", "readOnly": true }, "discount_save_offer_percent_off": { "type": [ "integer", "null" ], "format": "int64", "description": "Percent off the currently-active save-offer discount applies. Read\nlive by the BillingTab chip so a future coupon swap renders the new\nvalue without a code change.", "readOnly": true }, "has_payment_method": { "type": "boolean", "readOnly": true }, "last_discount_at": { "type": [ "string", "null" ], "format": "date-time", "description": "Most recent save-offer-discount application. NULL = never. Drives the\nonce-per-org eligibility check in `apply_discount_save_offer` and\nhides the Discount panel on the cancel modal for any return visit.", "readOnly": true }, "last_downgrade_at": { "type": [ "string", "null" ], "format": "date-time", "description": "Most recent downgrade event timestamp (paid→cheaper, or paid→cancelled);\npowers the 14-day downgrade banner.", "readOnly": true }, "last_downgrade_from_plan": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/BillingPlan", "description": "Plan downgraded from at `last_downgrade_at`; pairs with the timestamp\nso the banner can render \"you downgraded from Pro\"." } ] }, "last_paused_at": { "type": [ "string", "null" ], "format": "date-time", "description": "Most recent `Paused` billing event's timestamp; powers the 6-month\nrolling pause cooldown.", "readOnly": true }, "name": { "type": "string" }, "next_renewal_at": { "type": [ "string", "null" ], "format": "date-time", "description": "Stripe `subscription.items.data[0].current_period_end`, mirrored on\nevery billing event that re-anchors the period (checkout, trial start\n/ end, plan change, renewal, pause/resume, reactivate). Cleared by\nSubscriptionCancelled. Powers the \"Next renewal on …\" line in\nBillingPlanModal; the UI interprets the value based on plan_status\n(hide for paused/cancelled/past_due where the stored value can be\nstale or meaningless).", "readOnly": true }, "onboarding": { "type": "array", "items": { "$ref": "#/components/schemas/OnboardingOperationDiscriminants" } }, "plan": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/BillingPlan" } ] }, "plan_status": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/PlanStatus" } ] }, "trial_end_date": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true }, "trial_extended_used": { "type": "boolean", "description": "Whether the org has used its one-time trial-extend perk.", "readOnly": true }, "use_case": { "$ref": "#/components/schemas/UseCase", "description": "Use case selection (homelab, company, msp, other)" } } }, "PaginatedApiMeta": { "type": "object", "description": "API metadata for paginated list responses (pagination is always present)", "required": [ "api_version", "server_version", "pagination" ], "properties": { "api_version": { "type": "integer", "format": "int32", "description": "API version (integer, increments on breaking changes)", "minimum": 0 }, "pagination": { "$ref": "#/components/schemas/PaginationMeta", "description": "Pagination info" }, "server_version": { "type": "string", "description": "Server version (semver)", "example": "0.17.1" } }, "example": { "api_version": 1, "pagination": { "has_more": true, "limit": 50, "offset": 0, "total_count": 142 }, "server_version": "0.17.1" } }, "PaginatedApiResponse_Credential": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/CredentialBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_DaemonResponse": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/DaemonBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at", "version_status" ], "properties": { "created_at": { "type": "string", "format": "date-time" }, "id": { "type": "string", "format": "uuid" }, "updated_at": { "type": "string", "format": "date-time" }, "version_status": { "$ref": "#/components/schemas/DaemonVersionStatus", "description": "Computed version status including health and warnings" } } } ], "description": "Daemon response for UI including computed version status" } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_Dependency": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/DependencyBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "color": "Blue", "created_at": "2026-01-15T10:30:00Z", "dependency_type": "RequestPath", "description": "HTTP/HTTPS services dependency", "edge_style": "Bezier", "id": "550e8400-e29b-41d4-a716-446655440008", "lineage_id": null, "members": { "service_ids": [], "type": "Services" }, "name": "Web Services", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_HostResponse": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "type": "object", "description": "Response type for host endpoints.\nIncludes children (ip_addresses, ports, services, interfaces).", "required": [ "id", "created_at", "updated_at", "name", "network_id", "source", "hidden", "tags", "ip_addresses", "ports", "services", "interfaces" ], "properties": { "chassis_id": { "type": [ "string", "null" ] }, "created_at": { "type": "string", "format": "date-time" }, "credential_assignments": { "type": "array", "items": { "$ref": "#/components/schemas/CredentialAssignment" } }, "description": { "type": [ "string", "null" ] }, "hidden": { "type": "boolean" }, "hostname": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" }, "description": "SNMP ifTable entries" }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "management_url": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } }, "source": { "$ref": "#/components/schemas/EntitySource" }, "sys_contact": { "type": [ "string", "null" ] }, "sys_descr": { "type": [ "string", "null" ] }, "sys_location": { "type": [ "string", "null" ] }, "sys_object_id": { "type": [ "string", "null" ] }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "updated_at": { "type": "string", "format": "date-time" }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostVirtualization" } ] } }, "example": { "created_at": "2026-01-15T10:30:00Z", "credential_assignments": [], "description": "Primary web server", "hidden": false, "hostname": "web-server-01.local", "id": "550e8400-e29b-41d4-a716-446655440003", "interfaces": [ { "admin_status": "Up", "cdp_address": null, "cdp_device_id": null, "cdp_platform": null, "cdp_port_id": null, "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-44665544000f", "if_alias": "Uplink to Core Switch", "if_descr": "GigabitEthernet0/1", "if_index": 1, "if_name": "Gi0/1", "if_type": 6, "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "lldp_chassis_id": null, "lldp_mgmt_addr": null, "lldp_port_desc": null, "lldp_port_id": null, "lldp_sys_desc": null, "lldp_sys_name": null, "mac_address": "DE:AD:BE:EF:CA:FE", "neighbor": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "oper_status": "Up", "speed_bps": 1000000000, "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "ip_addresses": [ { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440005", "ip_address": "192.168.1.100", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "mac_address": "DE:AD:BE:EF:CA:FE", "name": "eth0", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "subnet_id": "550e8400-e29b-41d4-a716-446655440004", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "name": "web-server-01", "network_id": "550e8400-e29b-41d4-a716-446655440002", "ports": [ { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440006", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "number": 80, "protocol": "Tcp", "type": "Http", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } ], "services": [ { "bindings": [ { "created_at": "2026-06-29T15:15:28.313462Z", "first_discovery_id": null, "id": "3a2b94ca-0fc6-4c91-92c1-e1bcff3085ef", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.313462Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.313462Z", "valid_from": "2026-06-29T15:15:28.313462Z", "valid_to": null } ], "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440007", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "nginx", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "service_definition": "Frigate", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } ], "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "virtualization": null } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_Service": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/ServiceBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "bindings": [ { "created_at": "2026-06-29T15:15:28.337763Z", "first_discovery_id": null, "id": "5efb4fce-6581-4096-8af9-1da34b3e265f", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.337763Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.337763Z", "valid_from": "2026-06-29T15:15:28.337763Z", "valid_to": null } ], "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440007", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "nginx", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "service_definition": "Frigate", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_Subnet": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/SubnetBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "cidr": "192.168.1.0/24", "created_at": "2026-01-15T10:30:00Z", "description": "Local area network", "first_discovery_id": null, "id": "550e8400-e29b-41d4-a716-446655440004", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "LAN", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "subnet_type": "Lan", "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_Tag": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/TagBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "color": "Green", "created_at": "2026-01-15T10:30:00Z", "description": "Production environment resources", "id": "550e8400-e29b-41d4-a716-44665544000a", "is_application": false, "lineage_id": null, "name": "production", "organization_id": "550e8400-e29b-41d4-a716-446655440001", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_Topology": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/TopologyBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_User": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/UserBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_UserApiKey": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/UserApiKeyBase" }, { "type": "object", "required": [ "id", "updated_at", "created_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginatedApiResponse_Vlan": { "type": "object", "description": "Response type for paginated list endpoints (pagination is always present in meta)", "required": [ "success", "data", "meta" ], "properties": { "data": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/VlanBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ] } }, "error": { "type": [ "string", "null" ] }, "meta": { "$ref": "#/components/schemas/PaginatedApiMeta" }, "success": { "type": "boolean" } } }, "PaginationMeta": { "type": "object", "description": "Pagination metadata returned with paginated responses.", "required": [ "total_count", "limit", "offset", "has_more" ], "properties": { "has_more": { "type": "boolean", "description": "Whether there are more items after this page" }, "limit": { "type": "integer", "format": "int32", "description": "Maximum items per page (as requested)", "minimum": 0 }, "offset": { "type": "integer", "format": "int32", "description": "Number of items skipped", "minimum": 0 }, "total_count": { "type": "integer", "format": "int64", "description": "Total number of items matching the filter (ignoring pagination)", "minimum": 0 } }, "example": { "has_more": true, "limit": 50, "offset": 0, "total_count": 142 } }, "PaginationParams": { "type": "object", "description": "Pagination parameters that can be composed into filter queries.\n\nDefault behavior:\n- `limit`: 50 (returns up to 50 results)\n- `offset`: 0 (starts from the beginning)\n- `limit=0`: No limit (returns all results)\n- `limit` values above 1000 are capped to 1000", "properties": { "limit": { "type": [ "integer", "null" ], "format": "int32", "description": "Maximum number of results to return (1-1000, default: 50). Use 0 for no limit.", "minimum": 0 }, "offset": { "type": [ "integer", "null" ], "format": "int32", "description": "Number of results to skip. Default: 0.", "minimum": 0 } } }, "PauseDuration": { "type": "string", "description": "Pause subscription duration. The cancel modal's `RadioGroup` posts\none of these enum variants verbatim — no integer parsing at the API\nboundary, the type is the contract.", "enum": [ "days30", "days60", "days90" ] }, "PauseSubscriptionRequest": { "type": "object", "required": [ "duration_days" ], "properties": { "duration_days": { "$ref": "#/components/schemas/PauseDuration" } } }, "PlanConfig": { "type": "object", "required": [ "base_cents", "rate", "trial_days" ], "properties": { "base_cents": { "type": "integer", "format": "int64" }, "host_cents": { "type": [ "integer", "null" ], "format": "int64" }, "included_hosts": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 }, "included_networks": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 }, "included_seats": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 }, "network_cents": { "type": [ "integer", "null" ], "format": "int64" }, "rate": { "$ref": "#/components/schemas/BillingRate" }, "seat_cents": { "type": [ "integer", "null" ], "format": "int64" }, "trial_days": { "type": "integer", "format": "int32", "minimum": 0 } } }, "PlanStatus": { "type": "string", "description": "Derived subscription status — our domain enum, never Stripe's raw status.\nStripe webhook events map to typed `BillingOperation` variants at reception\n(in `billing/service.rs`); each variant deterministically implies a\n`PlanStatus` for downstream feature gates via\n`BillingOperation::implied_status`.\n\n`FromStr` is derived (via strum) so the storage layer can round-trip a\nsnake_case `text` column back into the typed value; `ToSchema` exposes\nthe enum as a stricter string union in the generated OpenAPI schema so\nthe frontend's `org.plan_status === 'paused'` comparisons are\ncompile-checked against the canonical variant list.", "enum": [ "active", "trialing", "past_due", "paused", "pending_cancellation", "cancelled" ] }, "PlanUsage": { "type": "object", "description": "Plan usage limits and current counts", "required": [ "host_count", "network_count", "seat_count" ], "properties": { "host_count": { "type": "integer", "format": "int64", "minimum": 0 }, "host_limit": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 }, "network_count": { "type": "integer", "format": "int64", "minimum": 0 }, "network_limit": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 }, "seat_count": { "type": "integer", "format": "int64", "minimum": 0 }, "seat_limit": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 } } }, "PodmanSubnetVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "service_id": { "type": "string", "format": "uuid", "description": "The Podman daemon service that owns this bridge network.\nDifferent Podman daemons on different hosts = distinct bridge subnets." } } }, "PodmanVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "compose_project": { "type": [ "string", "null" ] }, "container_id": { "type": [ "string", "null" ] }, "container_name": { "type": [ "string", "null" ] }, "service_id": { "type": "string", "format": "uuid" } } }, "Port": { "allOf": [ { "$ref": "#/components/schemas/PortBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "description": "Port entity with custom serialization that flattens PortType fields.", "example": { "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440006", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "number": 80, "protocol": "Tcp", "type": "Http", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "PortBase": { "allOf": [ { "$ref": "#/components/schemas/PortType" }, { "type": "object", "required": [ "host_id", "network_id" ], "properties": { "host_id": { "type": "string", "format": "uuid" }, "network_id": { "type": "string", "format": "uuid" } } } ], "description": "The base data for a Port entity (everything except id, created_at, updated_at)" }, "PortInput": { "type": "object", "description": "Input for creating or updating a port.\nUsed in both CreateHostRequest and UpdateHostRequest.\nClient must provide a UUID for the port.", "required": [ "id", "number", "protocol" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Client-provided UUID for this port" }, "number": { "type": "integer", "format": "int32", "description": "Port number (1-65535)", "minimum": 0 }, "protocol": { "$ref": "#/components/schemas/TransportProtocol", "description": "Transport protocol (Tcp or Udp)" } } }, "PortType": { "type": "object", "description": "Port type with number, protocol, and optional type identifier", "required": [ "number", "protocol" ], "properties": { "number": { "type": "integer" }, "protocol": { "type": "string", "enum": [ "Udp", "Tcp" ] }, "type": { "type": "string", "description": "Auto-derived from number+protocol; optional on create" } } }, "ProfileUpdateRequest": { "type": "object", "description": "Request to update user profile (deferred marketing fields)", "properties": { "company_size": { "type": [ "string", "null" ] }, "job_title": { "type": [ "string", "null" ] } } }, "ProvisionDaemonRequest": { "type": "object", "description": "Request to pre-provision a ServerPoll mode daemon.\nThis creates the daemon record on the server before the daemon is installed.", "required": [ "name", "network_id", "url" ], "properties": { "name": { "type": "string", "description": "Human-readable name for the daemon." }, "network_id": { "type": "string", "format": "uuid", "description": "Network this daemon will be associated with." }, "url": { "type": "string", "description": "URL where the server can reach the daemon (required for ServerPoll mode)." } } }, "ProvisionDaemonResponse": { "type": "object", "description": "Response from provisioning a daemon.\nContains the daemon record and the API key (shown only once).", "required": [ "daemon", "daemon_api_key" ], "properties": { "daemon": { "$ref": "#/components/schemas/DaemonResponse", "description": "The created daemon record (with version status)." }, "daemon_api_key": { "type": "string", "description": "The API key (plaintext) for daemon authentication.\nThis is shown only once - store it securely." } } }, "ProxmoxVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "service_id": { "type": "string", "format": "uuid" }, "vm_id": { "type": [ "string", "null" ] }, "vm_name": { "type": [ "string", "null" ] } } }, "PublicConfigResponse": { "type": "object", "required": [ "server_port", "disable_registration", "disable_password_login", "oidc_providers", "billing_enabled", "discount_save_offer_available", "has_integrated_daemon", "has_email_service", "has_email_opt_in", "public_url", "needs_cookie_consent", "deployment_type", "license_in_grace_period" ], "properties": { "billing_enabled": { "type": "boolean" }, "deployment_type": { "$ref": "#/components/schemas/DeploymentType" }, "disable_password_login": { "type": "boolean" }, "disable_registration": { "type": "boolean" }, "discount_save_offer_available": { "type": "boolean", "description": "`STRIPE_SAVE_OFFER_COUPON_ID` env var is set. When false, the\ncancel modal hides the discount save-offer panel so the user\ndoesn't see an option the deployment can't fulfil." }, "has_email_opt_in": { "type": "boolean" }, "has_email_service": { "type": "boolean" }, "has_integrated_daemon": { "type": "boolean" }, "license_expiry": { "type": [ "string", "null" ], "description": "Hard expiry — the drop-dead date after which the server rejects\nthe key. Referenced by the grace-period banner." }, "license_in_grace_period": { "type": "boolean", "description": "True when the license is past `intended_exp` but not yet past\nthe hard `exp` — the silent grace window." }, "license_intended_expiry": { "type": [ "string", "null" ], "description": "User-visible expiry — the date displayed to end users under\nnormal operation. 7 days earlier than `license_expiry` for keys\nissued after grace-period support landed." }, "license_status": { "type": [ "string", "null" ] }, "needs_cookie_consent": { "type": "boolean" }, "oidc_providers": { "type": "array", "items": { "$ref": "#/components/schemas/OidcProviderMetadata" } }, "posthog_key": { "type": [ "string", "null" ] }, "public_url": { "type": "string" }, "server_port": { "type": "integer", "format": "int32", "minimum": 0 }, "snapshot_retention_days_override": { "type": [ "integer", "null" ], "format": "int32", "description": "`SCANOPY_SNAPSHOT_RETENTION_DAYS_OVERRIDE` if set on this instance.\nFrontend uses it inside the plan-comparison view to display the\neffective retention for this deployment rather than the per-plan\nfixture default.", "minimum": 0 }, "stripe_publishable_key": { "type": [ "string", "null" ], "description": "Stripe publishable key, exposed so the frontend can mount Stripe\nElements (Payment Element) for in-app card collection. `None` when\nbilling isn't configured. Publishable keys are safe to expose to the\nbrowser (same as `posthog_key`)." } } }, "PublicShareMetadata": { "type": "object", "description": "Public share metadata (returned without authentication)", "required": [ "id", "name", "requires_password", "options", "enabled_views" ], "properties": { "enabled_views": { "type": "array", "items": { "$ref": "#/components/schemas/TopologyView" }, "description": "Resolved list of available topology views for this share.\nFiltered by both share configuration and data availability.\nFirst element is the default view." }, "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "options": { "$ref": "#/components/schemas/ShareOptions" }, "requires_password": { "type": "boolean" } } }, "ReferralSourceRequest": { "type": "object", "description": "Request to submit referral source", "required": [ "referral_source" ], "properties": { "referral_source": { "type": "string" }, "referral_source_other": { "type": [ "string", "null" ] } } }, "RegisterRequest": { "type": "object", "description": "Registration request from client", "required": [ "email", "password", "terms_accepted" ], "properties": { "company_url": { "type": [ "string", "null" ], "description": "Honeypot field for bot detection" }, "email": { "type": "string", "format": "email" }, "marketing_opt_in": { "type": "boolean" }, "password": { "type": "string" }, "terms_accepted": { "type": "boolean" } } }, "RequestEmailChangeRequest": { "type": "object", "required": [ "new_email" ], "properties": { "current_password": { "type": [ "string", "null" ], "description": "Current password — required if the user already has a password set.\nNot required for OIDC-only users." }, "new_email": { "type": "string", "format": "email" } } }, "ResendVerificationRequest": { "type": "object", "description": "Request to resend verification email", "required": [ "email" ], "properties": { "email": { "type": "string", "format": "email" } } }, "ResetPasswordRequest": { "type": "object", "required": [ "token", "password" ], "properties": { "password": { "type": "string" }, "token": { "type": "string" } } }, "RunType": { "oneOf": [ { "type": "object", "title": "Scheduled", "required": [ "cron_schedule", "enabled", "type" ], "properties": { "cron_schedule": { "type": "string" }, "enabled": { "type": "boolean" }, "last_run": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true }, "timezone": { "type": [ "string", "null" ], "description": "IANA timezone for cron evaluation, e.g. \"America/New_York\". None = UTC." }, "type": { "type": "string", "enum": [ "Scheduled" ] } } }, { "type": "object", "title": "Historical", "description": "Historical discovery runs are created by the server and cannot be submitted via API", "required": [ "results", "type" ], "properties": { "results": { "$ref": "#/components/schemas/DiscoveryUpdatePayload" }, "type": { "type": "string", "enum": [ "Historical" ] } } }, { "type": "object", "title": "AdHoc", "required": [ "type" ], "properties": { "last_run": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true }, "type": { "type": "string", "enum": [ "AdHoc" ] } } } ] }, "SaveOffer": { "type": "string", "description": "Save-offer choices presented during in-app cancellation (Phase 5).", "enum": [ "pause", "discount", "downgrade" ] }, "SaveOfferCoupon": { "type": "object", "description": "Live terms for the configured save-offer coupon, read directly from\nStripe. Used by the cancel modal's Discount panel to render the offer\ndynamically instead of hard-coding the percent/duration.\n\nOnly returned when the coupon would actually catch the user's next\ninvoice — i.e. `next_renewal_at` falls within the coupon's `duration_in_months`\nwindow. Yearly subscribers partway through a cycle whose next renewal\nlands after the coupon's window get `None` from the endpoint and the\ncancel modal's Discount panel doesn't render.\n\n`billing_rate` lets the frontend pick monthly vs yearly copy: a monthly\nsubscriber thinks in terms of \"N months of discount\"; a yearly subscriber\nthinks in terms of \"my next renewal on {date}.\"", "required": [ "percent_off", "duration_in_months", "next_renewal_at", "billing_rate" ], "properties": { "billing_rate": { "$ref": "#/components/schemas/BillingRate" }, "duration_in_months": { "type": "integer", "format": "int64" }, "next_renewal_at": { "type": "string", "format": "date-time" }, "percent_off": { "type": "integer", "format": "int64" } } }, "ScanSettings": { "type": "object", "description": "Scan performance settings. Lives on the discovery entity.\nNumeric fields are `Option` — `None` means \"use daemon default\".\nThe daemon unwraps with defaults at point of use.", "properties": { "arp_rate_pps": { "type": [ "integer", "null" ], "format": "int32", "description": "ARP packets per second (default: 50)", "minimum": 0 }, "arp_retries": { "type": [ "integer", "null" ], "format": "int32", "description": "ARP retry rounds for non-responsive targets (default: 2 = 3 total attempts)", "minimum": 0 }, "arp_scan_cutoff": { "type": [ "integer", "null" ], "format": "int32", "description": "ARP scan cutoff prefix. Interfaced subnets larger than this prefix are\ntruncated to this many IPs. Default: 15 (= /15, ~131K IPs).\nLower values scan more IPs — increase arp_rate_pps accordingly.", "minimum": 0 }, "full_scan_interval": { "type": [ "integer", "null" ], "format": "int32", "description": "Run a full 65k port scan every N scans. Other scans use a light port set.\nDefault: 3. Value of 0 means never full scan. Value of 1 means every scan is full.", "minimum": 0 }, "is_full_scan": { "type": "boolean", "description": "Whether this specific scan run should do a full 65k port scan.\nSet by the server before dispatching to the daemon — not user-configurable." }, "port_scan_batch_size": { "type": [ "integer", "null" ], "description": "Ports scanned concurrently per host (default: 200, clamped 16-1000)", "minimum": 0 }, "probe_raw_socket_ports": { "type": "boolean", "description": "Whether to probe raw-socket ports 9100-9107 (default: false).\nDisabled by default to prevent ghost printing on JetDirect printers." }, "scan_rate_pps": { "type": [ "integer", "null" ], "format": "int32", "description": "Port scan probes per second (default: 500)", "minimum": 0 }, "use_npcap_arp": { "type": "boolean", "description": "On Windows, use Npcap broadcast ARP instead of SendARP (default: false)" } } }, "ScannedEntityIds": { "type": "object", "description": "Canonical IDs of entities scanned in a discovery session.\n\nPopulated daemon-side at terminal phase from `EntityBuffer`'s `Created`\nentries. Travels with the terminal `DiscoveryUpdatePayload` to the server,\nrides the in-memory `EntityOperation::Created` event published for the\nhistorical Discovery row (the event scope carries `Entity::Discovery` with\nthe full struct, including `run_type::Historical { results }`), then is\nstripped before persisting into the historical Discovery row's JSONB (see\nthe `SqlValue::RunType` bind_value handler in\n`backend/src/server/shared/storage/generic.rs`). Per-entity-service\nsubscribers extract `results.scanned` from the in-memory event and call\n`DiscoveryFkUpdater::update_discovery_fks` to backfill\n`last_discovery_id` / `first_discovery_id` on the matched rows.\n\nNaming: `scanned_*` because the daemon scans entities — some submissions\nmatch existing rows (refresh), others insert new rows. Both populate the\nEntityBuffer with canonical (server-assigned) IDs.", "properties": { "binding_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "host_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "interface_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "ip_address_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "port_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "service_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "subnet_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "vlan_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } } } }, "SecretValue": { "oneOf": [ { "type": "object", "required": [ "value", "mode" ], "properties": { "mode": { "type": "string", "enum": [ "Inline" ] }, "value": { "type": "string" } } }, { "type": "object", "required": [ "path", "mode" ], "properties": { "mode": { "type": "string", "enum": [ "FilePath" ] }, "path": { "type": "string" } } } ], "description": "Secret value that can be either inline content or a file path on the daemon host." }, "ServerCapabilities": { "type": "object", "description": "Server capabilities returned on startup/registration", "required": [ "server_version", "minimum_daemon_version" ], "properties": { "deprecation_warnings": { "type": "array", "items": { "$ref": "#/components/schemas/DeprecationWarning" }, "description": "Deprecation warnings for the daemon" }, "minimum_daemon_version": { "type": "string", "description": "Minimum daemon version supported by this server" }, "server_version": { "type": "string", "description": "Server software version" } } }, "Service": { "allOf": [ { "$ref": "#/components/schemas/ServiceBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "bindings": [ { "created_at": "2026-06-29T15:15:28.325854Z", "first_discovery_id": null, "id": "516f258f-ca0e-4472-9a42-bd01bd35af92", "ip_address_id": "550e8400-e29b-41d4-a716-446655440005", "last_discovery_id": null, "last_seen_at": "2026-06-29T15:15:28.325854Z", "lineage_id": null, "network_id": "550e8400-e29b-41d4-a716-446655440002", "port_id": "550e8400-e29b-41d4-a716-446655440006", "service_id": "550e8400-e29b-41d4-a716-446655440007", "type": "Port", "updated_at": "2026-06-29T15:15:28.325854Z", "valid_from": "2026-06-29T15:15:28.325854Z", "valid_to": null } ], "created_at": "2026-01-15T10:30:00Z", "first_discovery_id": null, "host_id": "550e8400-e29b-41d4-a716-446655440003", "id": "550e8400-e29b-41d4-a716-446655440007", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "nginx", "network_id": "550e8400-e29b-41d4-a716-446655440002", "position": 0, "service_definition": "Frigate", "source": { "type": "Manual" }, "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null, "virtualization": null } }, "ServiceBase": { "type": "object", "required": [ "host_id", "network_id", "service_definition", "name", "bindings", "source", "tags", "position" ], "properties": { "bindings": { "type": "array", "items": { "$ref": "#/components/schemas/Binding" } }, "host_id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "position": { "type": "integer", "format": "int32", "description": "Position of this service in the host's service list (for ordering)" }, "service_definition": { "type": "string" }, "source": { "$ref": "#/components/schemas/EntitySource", "description": "Will be automatically set to Manual for creation through API" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceVirtualization" } ] } } }, "ServiceCategory": { "type": "string", "enum": [ "NetworkCore", "NetworkAccess", "NetworkAppliance", "RemoteAccess", "Storage", "Backup", "Media", "HomeAutomation", "Hypervisor", "ContainerRuntime", "Container", "Orchestrator", "DNS", "VPN", "Monitoring", "AdBlock", "ReverseProxy", "Workstation", "Mobile", "IoT", "Printer", "Database", "Development", "Dashboard", "MessageQueue", "IdentityAndAccess", "Integration", "Office", "ProjectManagement", "Messaging", "Conferencing", "Telephony", "Email", "Publishing", "Unknown", "Custom", "Scanopy", "OpenPorts" ] }, "ServiceInput": { "type": "object", "description": "Input for creating or updating a service.\nUsed in both CreateHostRequest and UpdateHostRequest.\nClient must provide a UUID for the service.", "required": [ "id", "service_definition", "name" ], "properties": { "bindings": { "type": "array", "items": { "$ref": "#/components/schemas/BindingInput" }, "description": "Bindings that associate this service with ports/interfaces" }, "id": { "type": "string", "format": "uuid", "description": "Client-provided UUID for this service" }, "name": { "type": "string", "description": "Display name for this service" }, "position": { "type": [ "integer", "null" ], "format": "int32", "description": "Position in the host's service list (for ordering).\nIf omitted on create: appends to end of list.\nIf omitted on update: existing services keep their positions; new services append.\nMust be all specified or all omitted across all services in the request." }, "service_definition": { "type": "string", "description": "Service definition ID (e.g., \"Nginx\", \"PostgreSQL\")" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Tags for categorization" }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceVirtualization", "description": "Container/VM virtualization info if applicable" } ] } } }, "ServiceOrderField": { "type": "string", "description": "Fields that services can be ordered/grouped by.", "enum": [ "created_at", "name", "updated_at", "host", "network_id", "position" ] }, "ServiceVirtualization": { "oneOf": [ { "type": "object", "title": "Docker", "required": [ "details", "type" ], "properties": { "details": { "$ref": "#/components/schemas/DockerVirtualization" }, "type": { "type": "string", "enum": [ "Docker" ] } } }, { "type": "object", "title": "Podman", "required": [ "details", "type" ], "properties": { "details": { "$ref": "#/components/schemas/PodmanVirtualization" }, "type": { "type": "string", "enum": [ "Podman" ] } } } ], "title": "ServiceVirtualization" }, "SetTagsRequest": { "type": "object", "description": "Request body for setting all tags on an entity", "required": [ "entity_type", "entity_id", "tag_ids" ], "properties": { "entity_id": { "type": "string", "format": "uuid", "description": "The entity ID" }, "entity_type": { "$ref": "#/components/schemas/EntityDiscriminants", "description": "The entity type (e.g., Host, Service, Subnet)" }, "tag_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "The new list of tag IDs" } } }, "SetupIntentResponse": { "type": "object", "description": "Response for creating a SetupIntent — the client secret the frontend\nPayment Element uses to collect and confirm a card in-app.", "required": [ "client_secret" ], "properties": { "client_secret": { "type": "string" } } }, "SetupRequest": { "type": "object", "description": "Setup request for pre-registration org/network configuration", "required": [ "organization_name", "network" ], "properties": { "network": { "$ref": "#/components/schemas/NetworkSetup" }, "organization_name": { "type": "string" } } }, "SetupResponse": { "type": "object", "description": "Response from setup endpoint", "required": [ "network_id" ], "properties": { "network_id": { "type": "string", "format": "uuid" } } }, "Share": { "allOf": [ { "$ref": "#/components/schemas/ShareBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "ShareAccessTokenResponse": { "type": "object", "description": "Access token returned after successful password verification.\n\nThe token is an HS256 JWT tied to the share's `password_hash` — changing\nthe share password implicitly invalidates all outstanding tokens.", "required": [ "access_token", "expires_at" ], "properties": { "access_token": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time" } } }, "ShareBase": { "type": "object", "required": [ "topology_id", "network_id", "created_by", "name", "is_enabled", "expires_at", "allowed_domains", "options", "enabled_views" ], "properties": { "allowed_domains": { "type": [ "array", "null" ], "items": { "type": "string" } }, "created_by": { "type": "string", "format": "uuid" }, "enabled_views": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/TopologyView" }, "description": "Which topology views are enabled for this share.\nNone = all views (subject to data availability). Some(list) = only these views in order.\nFirst element is the default view shown on load." }, "expires_at": { "type": [ "string", "null" ], "format": "date-time" }, "is_enabled": { "type": "boolean" }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "options": { "$ref": "#/components/schemas/ShareOptions" }, "password": { "type": [ "string", "null" ], "description": "Plaintext password on ingest; redacted sentinel (`\"********\"`) or `None` on egress.\nNever stored — `password_hash` is the DB column. Wrapped in `SecretString` so\n`Debug`/logging shows `[REDACTED]` during the window between request\ndeserialization and hashing." }, "topology_id": { "type": "string", "format": "uuid" } } }, "ShareOptions": { "type": "object", "description": "Share display options", "required": [ "show_inspect_panel", "show_zoom_controls", "show_export_button", "show_minimap" ], "properties": { "show_export_button": { "type": "boolean" }, "show_inspect_panel": { "type": "boolean" }, "show_minimap": { "type": "boolean" }, "show_zoom_controls": { "type": "boolean" } } }, "Snapshot": { "allOf": [ { "$ref": "#/components/schemas/SnapshotBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "SnapshotBase": { "type": "object", "required": [ "network_id", "taken_at" ], "properties": { "created_by_user_id": { "type": [ "string", "null" ], "format": "uuid" }, "network_id": { "type": "string", "format": "uuid" }, "taken_at": { "type": "string", "format": "date-time" } } }, "SnmpV3AuthProtocol": { "type": "string", "description": "SNMPv3 USM authentication protocol. Variants are limited to the modern,\nsecure set Scanopy supports; MD5 / SHA-2 variants beyond these are\nintentionally excluded. Serialized form (e.g. \"Sha256\") is the wire value\nstored in the credential and used as the frontend select option value.", "enum": [ "Sha1", "Sha256" ] }, "SnmpV3PrivProtocol": { "type": "string", "description": "SNMPv3 USM privacy (encryption) protocol.", "enum": [ "Aes128", "Aes256" ] }, "Subnet": { "allOf": [ { "$ref": "#/components/schemas/SubnetBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "cidr": "192.168.1.0/24", "created_at": "2026-01-15T10:30:00Z", "description": "Local area network", "first_discovery_id": null, "id": "550e8400-e29b-41d4-a716-446655440004", "last_discovery_id": null, "last_seen_at": "2026-01-15T10:30:00Z", "lineage_id": null, "name": "LAN", "network_id": "550e8400-e29b-41d4-a716-446655440002", "source": { "type": "Manual" }, "subnet_type": "Lan", "tags": [], "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "SubnetBase": { "type": "object", "required": [ "cidr", "network_id", "name", "subnet_type", "source", "tags" ], "properties": { "cidr": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/EntitySource", "description": "Will be automatically set to Manual for creation through API" }, "subnet_type": { "$ref": "#/components/schemas/SubnetType" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/SubnetVirtualization", "description": "Virtualization provider that owns this subnet.\nDocker bridge subnets use this for per-host dedup (same CIDR on different hosts = distinct)." } ] } } }, "SubnetOrderField": { "type": "string", "description": "Fields that subnets can be ordered/grouped by.", "enum": [ "created_at", "name", "cidr", "subnet_type", "updated_at", "network_id" ] }, "SubnetType": { "type": "string", "enum": [ "Internet", "Remote", "Gateway", "VpnTunnel", "Dmz", "Lan", "WiFi", "IoT", "Guest", "DockerBridge", "PodmanBridge", "MacVlan", "IpVlan", "Management", "Storage", "Loopback", "Unknown" ] }, "SubnetVirtualization": { "oneOf": [ { "allOf": [ { "$ref": "#/components/schemas/DockerSubnetVirtualization", "description": "Docker bridge network — host-scoped, same CIDR on different hosts are distinct subnets." }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Docker" ] } } } ], "title": "Docker", "description": "Docker bridge network — host-scoped, same CIDR on different hosts are distinct subnets." }, { "allOf": [ { "$ref": "#/components/schemas/PodmanSubnetVirtualization", "description": "Podman bridge network — host-scoped like Docker; same CIDR on different daemons are distinct subnets." }, { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "Podman" ] } } } ], "title": "Podman", "description": "Podman bridge network — host-scoped like Docker; same CIDR on different daemons are distinct subnets." } ], "description": "Virtualization metadata for subnets that belong to a virtual infrastructure.\nConsistent with HostVirtualization and ServiceVirtualization patterns.\nPoints to the service that provides the virtualization (e.g., Docker daemon)." }, "Tag": { "allOf": [ { "$ref": "#/components/schemas/TagBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ], "example": { "color": "Green", "created_at": "2026-01-15T10:30:00Z", "description": "Production environment resources", "id": "550e8400-e29b-41d4-a716-44665544000a", "is_application": false, "lineage_id": null, "name": "production", "organization_id": "550e8400-e29b-41d4-a716-446655440001", "updated_at": "2026-01-15T10:30:00Z", "valid_from": "2026-01-15T10:30:00Z", "valid_to": null } }, "TagBase": { "type": "object", "required": [ "name", "color", "organization_id" ], "properties": { "color": { "$ref": "#/components/schemas/Color" }, "description": { "type": [ "string", "null" ] }, "is_application": { "type": "boolean" }, "name": { "type": "string" }, "organization_id": { "type": "string", "format": "uuid" } } }, "TagOrderField": { "type": "string", "description": "Fields that tags can be ordered/grouped by.", "enum": [ "created_at", "name", "color", "updated_at", "is_application" ] }, "TestReachabilityRequest": { "type": "object", "description": "Request to test reachability of a daemon URL.", "required": [ "url" ], "properties": { "check_health": { "type": "boolean", "description": "If true, also perform an HTTP GET to {url}/health after the TCP check" }, "url": { "type": "string", "description": "Full URL of the daemon (e.g. \"https://daemon.example.com:60073\")" } } }, "TestReachabilityResponse": { "type": "object", "description": "Response from a reachability test.", "required": [ "reachable" ], "properties": { "error": { "type": [ "string", "null" ], "description": "Error message if not reachable" }, "health": { "type": [ "boolean", "null" ], "description": "Health check result (only present when check_health was true)" }, "reachable": { "type": "boolean", "description": "Whether the TCP connection succeeded" } } }, "Topology": { "allOf": [ { "$ref": "#/components/schemas/TopologyBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "TopologyBase": { "type": "object", "required": [ "network_id", "options" ], "properties": { "network_id": { "type": "string", "format": "uuid" }, "options": { "$ref": "#/components/schemas/TopologyOptions" } } }, "TopologyData": { "type": "object", "description": "Bundle of entities + the built graph that feed the topology render, export,\nand share pipelines.\n\nLoaded by [`crate::server::topology::service::main::TopologyService::get_topology_data`]\nfor either the live view (`snapshot_id = None`) or a point-in-time snapshot\n(`snapshot_id = Some(id)`). The per-view `nodes`/`edges` are built on request\nfrom these entities + the network's grouping options\n(`build_all_view_graphs`) — they are not persisted. The frontend selects the\nactive view's slice client-side.", "required": [ "hosts", "ip_addresses", "subnets", "dependencies", "ports", "bindings", "interfaces", "services", "vlans", "tags" ], "properties": { "available_views": { "type": "array", "items": { "$ref": "#/components/schemas/TopologyView" }, "description": "Views whose data is present in this entity set (L3/Workloads always;\nL2 Physical iff LLDP/CDP neighbors exist; Application iff app-flagged\ntags are used). The topology tab restricts a snapshot's view picker to\nthese — you can't set up SNMP or create app tags on a historical\nsnapshot — while the live view shows all views with setup prompts." }, "bindings": { "type": "array", "items": { "$ref": "#/components/schemas/Binding" } }, "dependencies": { "type": "array", "items": { "$ref": "#/components/schemas/Dependency" } }, "edges": { "type": "object", "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/Edge" } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } }, "hosts": { "type": "array", "items": { "$ref": "#/components/schemas/Host" } }, "interfaces": { "type": "array", "items": { "$ref": "#/components/schemas/Interface" } }, "ip_addresses": { "type": "array", "items": { "$ref": "#/components/schemas/IPAddress" } }, "nodes": { "type": "object", "description": "Per-view graph built on request from the entities above + grouping\noptions. Keyed by view so switching the active perspective is a\nclient-side slice selection.", "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/Node" } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/Port" } }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } }, "subnets": { "type": "array", "items": { "$ref": "#/components/schemas/Subnet" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } }, "vlans": { "type": "array", "items": { "$ref": "#/components/schemas/Vlan" } } } }, "TopologyLocalOptions": { "type": "object", "properties": { "bundle_edges": { "type": "boolean", "default": true }, "hide_edge_types": { "type": "array", "items": { "$ref": "#/components/schemas/EdgeTypeDiscriminants" }, "default": [ "Hypervisor" ] }, "no_fade_edges": { "type": "boolean", "default": false }, "show_minimap": { "type": "boolean", "default": true }, "tag_filter": { "oneOf": [ { "$ref": "#/components/schemas/TopologyTagFilter" } ], "default": { "hidden_host_tag_ids": [], "hidden_service_tag_ids": [], "hidden_subnet_tag_ids": [] } } } }, "TopologyOptions": { "type": "object", "required": [ "local", "request" ], "properties": { "local": { "$ref": "#/components/schemas/TopologyLocalOptions" }, "request": { "$ref": "#/components/schemas/TopologyRequestOptions" } } }, "TopologyRequestOptions": { "type": "object", "properties": { "container_rules": { "type": "object", "default": { "Application": [ { "id": "82796dc5-d053-4da2-882c-ab05c3582510", "rule": { "ByApplication": { "tag_ids": [] } } } ], "L2Physical": [ { "id": "44599b74-53db-47f3-bf3b-770b778013de", "rule": "ByHost" } ], "L3Logical": [ { "id": "17a68964-60c7-4fd3-be99-37f94bdf356a", "rule": "BySubnet" }, { "id": "097c65f1-0a73-476c-9bd5-0513e72b8c0b", "rule": "MergeDockerBridges" } ], "Workloads": [ { "id": "44599b74-53db-47f3-bf3b-770b778013de", "rule": "ByHost" } ] }, "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/IdentifiedRule_ContainerRule" } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } }, "element_rules": { "type": "array", "items": { "$ref": "#/components/schemas/IdentifiedRule_ElementRule" }, "default": [ { "id": "b6481282-4036-44fc-9aa5-24b09985199d", "rule": "ByTrunkPort" }, { "id": "8e701dbf-f93b-4691-bc6e-a082dcfc780f", "rule": "ByVLAN" }, { "id": "8d2f1998-c834-4452-a149-84c94da6d16b", "rule": "ByPortOpStatus" }, { "id": "c8e0bca8-2389-4a45-b310-0d7f9f5d237e", "rule": { "ByServiceCategory": { "categories": [ "NetworkCore", "NetworkAccess", "RemoteAccess", "Workstation", "Mobile", "Printer", "OpenPorts" ], "is_infra_rule": true, "title": "Infrastructure" } } }, { "id": "51651c5b-baba-4514-b7f8-46630335266d", "rule": { "ByTag": { "tag_ids": [], "title": null } } }, { "id": "7cc27b22-6067-4ad5-8548-89ad57ee264c", "rule": "ByHypervisor" }, { "id": "61e59024-1212-4ff9-af44-32ae69269e66", "rule": "ByContainerRuntime" }, { "id": "91d6f69d-c646-4c6f-b68c-945389d5feba", "rule": "ByStack" } ] }, "hide_entities": { "type": "object", "description": "Entity types hidden per view. Keyed by TopologyView, values are entity\ntypes (matching those declared as container/element/inline in the\nview's element_config). Hides every manifestation of the entity in\nthat view — element nodes, container nodes, and inline rows on\nelement cards. Supersedes the old `hide_ports` (L3-only, inline-only).", "default": {}, "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/EntityDiscriminants" } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } }, "hide_metadata_values": { "type": "object", "description": "Generic per-(view, entity, filter) hide-set for metadata filters\n(Category, Virtualization, etc). Supersedes the old\n`hide_service_categories`; nested so JSON keys are strings all the\nway down.", "default": { "Application": { "Service": { "Category": [ "OpenPorts" ] } }, "L2Physical": { "Service": { "Category": [ "OpenPorts" ] } }, "L3Logical": { "Service": { "Category": [ "OpenPorts" ] } }, "Workloads": { "Service": { "Category": [ "OpenPorts" ] } } }, "additionalProperties": { "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } }, "propertyNames": { "type": "string", "description": "The kind of metadata filter. One variant per conceptually-distinct filter\nacross the app — Category (on Service), Virtualization (on Host), and so\non. Kept narrow on purpose: adding a new filter means adding a variant\nhere + a `HasFilterValues` impl on the relevant entity.", "enum": [ "Category", "Virtualization" ] } }, "propertyNames": { "type": "string", "enum": [ "Organization", "Invite", "Share", "Network", "DaemonApiKey", "UserApiKey", "User", "Tag", "Discovery", "Daemon", "Host", "Service", "Port", "Binding", "IPAddress", "Interface", "Credential", "Subnet", "Vlan", "Dependency", "Topology", "Snapshot", "Unknown" ] } }, "propertyNames": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] } } } }, "TopologyTagFilter": { "type": "object", "description": "Filter settings for hiding entities by tag in topology visualization.", "properties": { "hidden_host_tag_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Host tag IDs to hide (hosts with these tags will fade out)" }, "hidden_service_tag_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Service tag IDs to hide (services with these tags will be hidden from nodes)" }, "hidden_subnet_tag_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Subnet tag IDs to hide (subnets with these tags will fade out)" } } }, "TopologyView": { "type": "string", "description": "Which topology view is being rendered", "enum": [ "L2Physical", "L3Logical", "Workloads", "Application" ] }, "TransportProtocol": { "type": "string", "enum": [ "Udp", "Tcp" ] }, "UpdateHostRequest": { "type": "object", "description": "Request type for updating a host with its children.\nUses the same input types as CreateHostRequest.\nServer will sync children (create new, update existing, delete removed) only if provided.", "required": [ "id", "name", "hidden", "tags" ], "properties": { "credential_assignments": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/CredentialAssignment" }, "description": "Credential assignments for this host.\nIf provided, replaces all existing credential assignments." }, "description": { "type": [ "string", "null" ] }, "expected_updated_at": { "type": [ "string", "null" ], "format": "date-time", "description": "Optional: expected updated_at timestamp for optimistic locking." }, "hidden": { "type": "boolean" }, "hostname": { "type": [ "string", "null" ] }, "id": { "type": "string", "format": "uuid" }, "ip_addresses": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/IPAddressInput" }, "description": "Interfaces to sync with this host.\nIf Some, server will create/update/delete to match this list.\nIf None, existing ip_addresses are preserved." }, "name": { "type": "string" }, "ports": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/PortInput" }, "description": "Ports to sync with this host.\nIf Some, server will create/update/delete to match this list.\nIf None, existing ports are preserved." }, "services": { "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/ServiceInput" }, "description": "Services to sync with this host.\nIf Some, server will create/update/delete to match this list.\nIf None, existing services are preserved." }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "virtualization": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/HostVirtualization" } ] } } }, "UpdatePasswordRequest": { "type": "object", "required": [ "new_password" ], "properties": { "current_password": { "type": [ "string", "null" ], "description": "Current password — required if the user already has a password set.\nNot required for OIDC-only users adding their first password." }, "new_password": { "type": "string", "description": "New password to set" } } }, "UseCase": { "type": "string", "enum": [ "homelab", "internal_it", "msp", "other" ] }, "User": { "allOf": [ { "$ref": "#/components/schemas/UserBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "UserApiKey": { "allOf": [ { "$ref": "#/components/schemas/UserApiKeyBase" }, { "type": "object", "required": [ "id", "updated_at", "created_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true } } } ] }, "UserApiKeyBase": { "type": "object", "required": [ "key", "name", "user_id", "organization_id", "last_used", "tags" ], "properties": { "expires_at": { "type": [ "string", "null" ], "format": "date-time" }, "is_enabled": { "type": "boolean" }, "key": { "type": "string", "readOnly": true }, "last_used": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true }, "name": { "type": "string" }, "network_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Network IDs this key has access to (hydrated from junction table)" }, "organization_id": { "type": "string", "format": "uuid" }, "permissions": { "$ref": "#/components/schemas/UserOrgPermissions" }, "tags": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "user_id": { "type": "string", "format": "uuid" } } }, "UserApiKeyResponse": { "type": "object", "description": "Response for user API key creation/rotation\nContains the full API key record plus the plaintext key (shown only once)", "required": [ "api_key", "key" ], "properties": { "api_key": { "$ref": "#/components/schemas/UserApiKey" }, "key": { "type": "string", "description": "The plaintext API key - only returned once during creation or rotation" } } }, "UserBase": { "type": "object", "required": [ "email", "organization_id", "permissions", "network_ids" ], "properties": { "email": { "type": "string" }, "email_settings": { "$ref": "#/components/schemas/EmailSettings", "description": "Per-user email preferences" }, "email_verified": { "type": "boolean", "description": "Whether the user has verified their email address" }, "has_password": { "type": "boolean", "description": "Whether the user has a password set — computed from password_hash, never stored in DB", "readOnly": true }, "network_ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "oidc_linked_at": { "type": [ "string", "null" ], "format": "date-time" }, "oidc_provider": { "type": [ "string", "null" ] }, "organization_id": { "type": "string", "format": "uuid" }, "permissions": { "$ref": "#/components/schemas/UserOrgPermissions" }, "terms_accepted_at": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } }, "UserOrgPermissions": { "type": "string", "enum": [ "Owner", "Admin", "Member", "Viewer" ] }, "Uxy": { "type": "object", "description": "2D unsigned coordinate. Used for node positions and sizes.\nElement node sizes are computed by the frontend (elkjs); the backend\nsets `Uxy::default()` for element nodes.", "required": [ "x", "y" ], "properties": { "x": { "type": "integer", "minimum": 0 }, "y": { "type": "integer", "minimum": 0 } } }, "VCenterVirtualization": { "type": "object", "required": [ "service_id" ], "properties": { "service_id": { "type": "string", "format": "uuid" }, "vm_id": { "type": [ "string", "null" ] }, "vm_name": { "type": [ "string", "null" ] } } }, "VerifyEmailRequest": { "type": "object", "description": "Request to verify email using token", "required": [ "token" ], "properties": { "token": { "type": "string" } } }, "VersionHealthStatus": { "type": "string", "description": "Health status for daemon versions", "enum": [ "Current", "Outdated", "Deprecated" ] }, "VersionInfo": { "type": "object", "description": "Version information for API compatibility checking", "required": [ "api_version", "server_version" ], "properties": { "api_version": { "type": "integer", "format": "int32", "description": "Current API version (integer, increments on breaking changes)", "minimum": 0 }, "min_compatible_client": { "type": [ "string", "null" ], "description": "Minimum client version that can use this API (optional, for future use)" }, "server_version": { "type": "string", "description": "Server version (semver)", "example": "0.12.10" } } }, "Vlan": { "allOf": [ { "$ref": "#/components/schemas/VlanBase" }, { "type": "object", "required": [ "id", "created_at", "updated_at" ], "properties": { "created_at": { "type": "string", "format": "date-time", "readOnly": true }, "first_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "id": { "type": "string", "format": "uuid", "readOnly": true }, "last_discovery_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "last_seen_at": { "type": "string", "format": "date-time", "readOnly": true }, "lineage_id": { "type": [ "string", "null" ], "format": "uuid", "readOnly": true }, "updated_at": { "type": "string", "format": "date-time", "readOnly": true }, "valid_from": { "type": "string", "format": "date-time", "readOnly": true }, "valid_to": { "type": [ "string", "null" ], "format": "date-time", "readOnly": true } } } ] }, "VlanBase": { "type": "object", "required": [ "vlan_number", "name", "network_id", "organization_id" ], "properties": { "description": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "network_id": { "type": "string", "format": "uuid" }, "organization_id": { "type": "string", "format": "uuid" }, "source": { "$ref": "#/components/schemas/EntitySource" }, "vlan_number": { "type": "integer", "format": "int32", "description": "The 802.1Q VLAN number (1-4094)", "minimum": 0 } } }, "VlanDiscoveryItem": { "type": "object", "required": [ "vlan_number", "name" ], "properties": { "name": { "type": "string" }, "vlan_number": { "type": "integer", "format": "int32", "minimum": 0 } } }, "VlanDiscoveryRequest": { "type": "object", "description": "Request body for daemon VLAN discovery upsert", "required": [ "network_id", "vlans" ], "properties": { "network_id": { "type": "string", "format": "uuid" }, "vlans": { "type": "array", "items": { "$ref": "#/components/schemas/VlanDiscoveryItem" } } } }, "VlanDiscoveryResponse": { "type": "object", "description": "Response for discovery upsert", "required": [ "vlans" ], "properties": { "vlans": { "type": "array", "items": { "$ref": "#/components/schemas/VlanDiscoveryResponseItem" }, "description": "Mapping of vlan_number → VLAN entity UUID" } } }, "VlanDiscoveryResponseItem": { "type": "object", "required": [ "vlan_number", "id" ], "properties": { "id": { "type": "string", "format": "uuid" }, "vlan_number": { "type": "integer", "format": "int32", "minimum": 0 } } }, "VlanOrderField": { "type": "string", "enum": [ "created_at", "name", "vlan_number", "updated_at" ] } }, "securitySchemes": { "daemon_api_key": { "type": "apiKey", "in": "header", "name": "Authorization", "description": "Daemon API key (Bearer scp_d_...). Requires X-Daemon-ID header." }, "session": { "type": "apiKey", "in": "cookie", "name": "session_id", "description": "Browser session cookie. Obtained via /api/auth/login." }, "user_api_key": { "type": "apiKey", "in": "header", "name": "Authorization", "description": "User API key (Bearer scp_u_...). Create in Platform > API Keys." } } }, "tags": [ { "name": "Bindings", "description": "Service bindings linking services to IP addresses and/or ports. Defines where a service is accessible." }, { "name": "Daemons", "description": "Daemons are scanning agents that connect to the server to perform network discovery." }, { "name": "Daemon API Keys", "description": "API keys for daemon authentication. Create and manage keys that allow daemons to communicate with the server." }, { "name": "Discoveries", "description": "Network discovery operations. Trigger and monitor scans that detect hosts, services, and network topology." }, { "name": "Dependencies", "description": "Service dependency relationships. Define how services depend on each other." }, { "name": "Hosts", "description": "Network hosts (devices). Manage discovered or manually created hosts on your network." }, { "name": "Interfaces", "description": "SNMP ifTable entries. Physical and logical interfaces discovered via SNMP on hosts." }, { "name": "IP Addresses", "description": "IP addresses assigned to hosts. Each address belongs to a host and a subnet, optionally has a MAC address, and represents an observed or configured address on the network." }, { "name": "Invites", "description": "Organization invitations. Invite users to join your organization." }, { "name": "Networks", "description": "Network containers. Top-level organizational unit that contains subnets, hosts, and other entities." }, { "name": "Organizations", "description": "Manage organization settings." }, { "name": "Ports", "description": "Ports that have been scanned and found open on a host." }, { "name": "Services", "description": "Services running on hosts. Detected or manually added services like databases, web servers, etc." }, { "name": "Shares", "description": "Shared network views. Create read-only shareable links to your network topology." }, { "name": "Credentials", "description": "Credentials for network device discovery and management. Supports SNMP, Docker proxy, and other credential types." }, { "name": "Subnets", "description": "IP subnets within networks. Define address ranges and organize hosts by subnet." }, { "name": "Tags", "description": "Custom tags for categorization. Apply labels to entities for filtering and organization." }, { "name": "Topologies", "description": "Network topology maps showing host relationships and connections." }, { "name": "Users", "description": "User account management. Manage user profiles and permissions within organizations." }, { "name": "User API Keys", "description": "User API keys for programmatic access. Create and manage personal API keys with scoped permissions." }, { "name": "Vlans", "description": "VLANs (802.1Q virtual LANs) defined or discovered on the network. Each VLAN has a number (1-4094), a name, and an optional description, and is referenced by interfaces that participate in it." }, { "name": "auth", "description": "Authentication and session management. Handle user login, logout, and session state." }, { "name": "config", "description": "Server configuration. Public configuration settings for client applications." }, { "name": "github", "description": "GitHub integration endpoints." }, { "name": "internal", "description": "Internal endpoints for system operations. Not part of the public API." }, { "name": "metadata", "description": "Entity metadata registry. Schema information for all entity types in the system." }, { "name": "system", "description": "System information endpoints. Version and compatibility checking." } ] }