{"openapi":"3.1.0","info":{"title":"Ethiack API","description":"Ethiack unified API — security testing platform.","termsOfService":"https://ethiack.com/terms","contact":{"name":"Support","email":"support@ethiack.com"},"version":"1.0.0"},"paths":{"/v1/me":{"get":{"tags":["general"],"summary":"Current authenticated identity","description":"Return the caller's identity profile.\n\nResolves the caller's own ``users`` row by actor id; 404 when the\ncaller has no backing user row (e.g. machine/system actors).\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    UserResponse: The authenticated user's public profile.","operationId":"general-me","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}}}},"/v1/assets/":{"get":{"tags":["assets"],"summary":"List assets","description":"List assets visible to the caller, filtered and paginated.\n\nMirrors api-3's ``PaginatedAssetsFilter`` (``asset_id``,\n``organization_id``, ``page``, ``per_page``). Authorization scoping\nis enforced by ``AssetService``; the route only translates transport.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (AssetListQuery): Filters + pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    AssetListResponse: The authorized, paginated asset list.","operationId":"assets-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Asset Id"}},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["assets"],"summary":"Create an asset","description":"Create an asset, after the ownership gate.\n\nAudience (organization, partner) is enforced by the asset policy.\nThe gate is the workflow's: a domain or public IP/CIDR needs an\nactive verification covering it (legacy answered 403 \"Unverified\ndomain\"); an integrator flagged to vouch for its clients gets the\nproof minted for it instead -- legacy's ownership bypass, recorded as\na self-attestation. ``organization_id`` defaults to the caller's org.\n\nArgs:\n    body (CreateAssetRequest): Asset creation payload.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy create envelope carrying the new id.\n\nRaises:\n    ValueError: No ``organization_id`` was given and the caller has no\n        default organization.\n    PermissionError: No active verification covers the target and the\n        caller may not have one minted, or Cerbos denied the create.","operationId":"assets-create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAssetRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/assets/export":{"get":{"tags":["assets"],"summary":"Export assets as CSV","description":"Export every asset the caller may see as CSV.\n\nA ``type,value,beacon`` header precedes one row per asset, where\n``type`` is the export label\n(see :func:`_export_type_label`), ``value`` is the asset value, and\n``beacon`` is the owning beacon's name (empty for non-beacon assets).\nAssets are Cerbos-scoped by ``AssetService``; beacon names come from a\nsecond Cerbos-scoped ``BeaconService`` read, skipped (leaving the column\nempty) when the plan lacks the ``beacon`` feature. Built in memory and\nreturned inline (see ``scans.py`` for why this is not the async\ndocuments pipeline).\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (AssetExportQuery): Optional ``organization_id`` filter.\n\nReturns:\n    Response: A ``text/csv`` download of the authorized assets.","operationId":"assets-export","parameters":[{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}}],"responses":{"200":{"description":"CSV export of every asset visible to the caller, with a ``type,value,beacon`` header.","content":{"text/csv":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/assets/{asset_id}":{"get":{"tags":["assets"],"summary":"Get a single asset","description":"Return one asset by id (404 if not found or not authorized).\n\nArgs:\n    asset_id (int): Primary key of the asset.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    AssetDetailResponse: The requested asset.","operationId":"assets-get","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["assets"],"summary":"Update an asset","description":"Update an asset (e.g. its tier).\n\nArgs:\n    asset_id (int): Primary key of the asset.\n    body (UpdateAssetRequest): Partial asset update payload.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename update envelope.","operationId":"assets-update","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["assets"],"summary":"Delete an asset","description":"Delete an asset.\n\nSoft-deletes the asset and clears what would otherwise dangle off\nit -- scope pivots, network-service links, draft findings and the\nstored attachment -- through :class:`DeleteAssetWorkflow`. Submitted\nfindings and scan-asset snapshots are kept. Calling\n``AssetService.soft_delete`` here instead would flip the asset's own\nflag and leave every dependent behind.\n\nArgs:\n    asset_id (int): Primary key of the asset.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy delete envelope, echoing the removed id.\n\nRaises:\n    NotFoundError: No such asset is visible to the caller.","operationId":"assets-delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/assets/{asset_id}/services":{"get":{"tags":["assets"],"summary":"List an asset's network services","description":"Return the network services discovered on one asset.\n\nThe same set the asset detail response embeds, on its own endpoint so\na panel can refresh services without re-fetching the asset. Reads the\n``asset_network_service_details`` view through its Cerbos-scoped\nservice, so another organization's asset yields an empty list rather\nthan a denial — the asset itself is resolved first, so an asset the\ncaller cannot see still 404s.\n\nArgs:\n    asset_id (int): Primary key of the owning asset.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (AssetServiceListQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    AssetServiceListResponse: The asset's services, each with its\n        detected technologies.","operationId":"assets-services-list","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetServiceListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/domains/":{"get":{"tags":["domains"],"summary":"List domains","description":"List the domains visible to the caller.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (DomainListQuery): Filters + pagination.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    DomainListResponse: The authorized, paginated list.","operationId":"domains-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact domain value (case-insensitive).","title":"Value"},"description":"Exact domain value (case-insensitive)."},{"name":"verified","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by verification status.","title":"Verified"},"description":"Filter by verification status."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"enum":["domain_checkbox","domain_dns"],"type":"string"},{"type":"null"}],"description":"Restrict to one proof method: `domain_dns` (TXT record) or `domain_checkbox` (self-attestation). Both when omitted.","title":"Kind"},"description":"Restrict to one proof method: `domain_dns` (TXT record) or `domain_checkbox` (self-attestation). Both when omitted."},{"name":"domain_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by the domain's unique identifier.","title":"Domain Id"},"description":"Filter by the domain's unique identifier."},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by organization ID.","title":"Organization Id"},"description":"Filter by organization ID."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["domains"],"summary":"Add a domain","description":"Create a pending DNS-verified domain.\n\nArgs:\n    body (CreateDomainRequest): Creation payload.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy create envelope carrying the new id.\n\nRaises:\n    ValueError: The request named the checkbox kind (self-attestation\n        is a portal action), no ``organization_id`` was given and the\n        caller has no default organization, or ``value`` is not a\n        domain. Answered 400.\n    PermissionError: Cerbos denied the create.\n    ConflictError: The org already has a DNS proof for this value.","operationId":"domains-create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDomainRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/domains/{domain_id}":{"get":{"tags":["domains"],"summary":"Get a domain","description":"Return one domain by id (404 if not found or not authorized).\n\nArgs:\n    domain_id (int): The domain's identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    DomainResponse: The requested domain.","operationId":"domains-get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["domains"],"summary":"Delete a domain","description":"Delete a domain, and by default the assets no other proof covers.\n\nThe optional body's ``delete_assets`` controls the cascade; omitting\nthe body (as every legacy client does) keeps legacy's behaviour and\nremoves the assets under the domain that no other active proof still\ncovers.\n\nArgs:\n    domain_id (int): The domain's identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    body (DeleteDomainRequest | None): Optional cascade switch.\n\nReturns:\n    SuccessResponse: The legacy delete envelope, echoing the removed id.\n\nRaises:\n    NotFoundError: No domain with that id is visible to the caller.\n    PermissionError: Cerbos denied the composite delete.","operationId":"domains-delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DeleteDomainRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/domains/{domain_id}/verify":{"post":{"tags":["domains"],"summary":"Verify domain ownership","description":"Run the DNS TXT-record check and mark the domain verified on success.\n\nAn already verified domain, of either kind, answers success at once\nwithout re-running any proof, as legacy did.\n\nArgs:\n    domain_id (int): The domain's identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy envelope, ``\"Domain verified\"``.\n\nRaises:\n    VerificationFailedError: The lookup answered and the expected TXT\n        record was absent -- answered 422, the documented code, so the\n        caller knows to publish it.\n    DnsLookupError: No resolver could answer, so the proof was not\n        attempted -- answered 503 with ``Retry-After``, never 422,\n        which would blame the caller's DNS for our own outage.\n    PermissionError: Cerbos denied the activate.","operationId":"domains-verify","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/attachments/{attachment_id}/view":{"get":{"tags":["findings"],"summary":"View a finding attachment inline","description":"Redirect to a signed URL carrying the inline disposition.\n\nOnly common image and video types are served inline; any other type is\nserved as a download.\n\nArgs:\n    attachment_id (int): The attachment's primary key.\n    db (AsyncSession): The request session.\n    context (ActiveContext): The resolved caller.\n\nReturns:\n    RedirectResponse: A 302 to the short-lived signed GCS URL.","operationId":"finding-attachments-view","parameters":[{"name":"attachment_id","in":"path","required":true,"schema":{"type":"integer","title":"Attachment Id"}}],"responses":{"302":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/attachments/{attachment_id}/download":{"get":{"tags":["findings"],"summary":"Download a finding attachment","description":"Redirect to a signed URL carrying the attachment disposition.\n\nArgs:\n    attachment_id (int): The attachment's primary key.\n    db (AsyncSession): The request session.\n    context (ActiveContext): The resolved caller.\n\nReturns:\n    RedirectResponse: A 302 to the short-lived signed GCS URL.","operationId":"finding-attachments-download","parameters":[{"name":"attachment_id","in":"path","required":true,"schema":{"type":"integer","title":"Attachment Id"}}],"responses":{"302":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/":{"get":{"tags":["findings"],"summary":"List findings","description":"List findings visible to the caller, filtered and paginated.\n\nScoped to the caller by ``FindingService``; supports filtering by\norganization, pentest, asset, and CVE. The route only translates\ntransport in/out.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (FindingListQuery): Filters + pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    FindingListResponse: The authorized, filtered finding list.","operationId":"findings-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}},{"name":"finding_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Finding Id"}},{"name":"pentest_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"}},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Asset Id"}},{"name":"cve","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cve"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FindingStatus"},{"type":"null"}],"title":"Status"}},{"name":"statuses","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/FindingStatus"}},{"type":"null"}],"title":"Statuses"}},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Severity"},{"type":"null"}],"title":"Severity"}},{"name":"severities","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Severity"}},{"type":"null"}],"title":"Severities"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindingListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/{finding_id}/":{"get":{"tags":["findings"],"summary":"Get a single finding","description":"Return one finding by id (404 if not found or not authorized).\n\nArgs:\n    finding_id (int): Primary key of the finding.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    FindingDetailResponse: The requested finding.","operationId":"findings-get","parameters":[{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindingDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/{finding_id}/status":{"post":{"tags":["findings"],"summary":"Change a finding's status","description":"Change the status of a finding.\n\nAvailable to organizations and machine callers; the transition is\nvalidated and authorized by the finding status-change service, and\nnoted in the finding's Updates thread.\n\nArgs:\n    finding_id (int): Primary key of the finding.\n    body (UpdateFindingStatusRequest): Requested status transition.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    UpdateFindingStatusResponse: The finding with its new status.","operationId":"findings-status","parameters":[{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFindingStatusRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFindingStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/{finding_id}/metadata/":{"get":{"tags":["findings"],"summary":"Get a finding's metadata","description":"Return the metadata blob for a finding.\n\nMachine, triager and privileged callers only: any other caller gets 403\nfor a finding it can read, and, like every caller, 404 for one it cannot.\n\nArgs:\n    finding_id (int): Primary key of the finding.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    FindingMetadataResponse: The finding's metadata.","operationId":"findings-metadata","parameters":[{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindingMetadataResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/findings/{finding_id}/retest/":{"get":{"tags":["findings"],"summary":"Get retest information for a finding","description":"Return retest eligibility and state for a finding.\n\nReports whether the finding can be retested and, if a retest is\nalready in progress, its current status.\n\nArgs:\n    finding_id (int): Primary key of the finding.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    RetestInfoResponse: Retest eligibility and state.","operationId":"findings-retest-info","parameters":[{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetestInfoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["findings"],"summary":"Start a retest for a finding","description":"Start a retest job for a finding.\n\nConflicts (409) when a retest already exists or the finding is not\nre-testable; rate-limited requests yield 429.\n\nArgs:\n    finding_id (int): Primary key of the finding.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename envelope, naming the retest\n        job by its uuid rather than the finding.","operationId":"findings-retest-start","parameters":[{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/scans/export":{"get":{"tags":["scans"],"summary":"Export scans as CSV","description":"Export every scan the caller may see as a CSV attachment.\n\nRows are Cerbos-scoped by ``ScanService`` (an org user gets their\norg's scans, an admin gets all), sorted newest-first by\n``init_time`` with unstarted scans last, and enriched with the\nowning pentest's name via a second Cerbos-scoped\n``PentestService`` read. The whole document is built in memory and\nreturned inline — see the module docstring for why this is not the\nasync documents pipeline.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (ScanExportQuery): Optional ``pentest_id`` / ``launch_id``\n        filters.\n\nReturns:\n    Response: A ``text/csv`` download of the authorized scans.","operationId":"scans-export","parameters":[{"name":"pentest_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pentest Id"}}],"responses":{"200":{"description":"CSV export of every scan visible to the caller.","content":{"text/csv":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/":{"get":{"tags":["organizations"],"summary":"List organizations","description":"List organizations visible to the caller.\n\nResults are scoped and paginated by ``OrganizationService``; the\nroute only translates transport in/out.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    OrganizationListResponse: The authorized, paginated organization list.","operationId":"organizations-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["organizations"],"summary":"Create an organization","description":"Create an organization on behalf of an integrator's partner.\n\nRestricted to integrators; the caller's own ``is_integrator``\npartner becomes the owning partner of the new organization. Seeds\nan ``INTEGRATOR_ORG`` subscription from the ``\"integrated\"``\ncatalog plan, capped by the partner's own asset limit.\n\nArgs:\n    body (CreateOrganizationRequest): Organization creation payload.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    partner_id (int): The caller's own integrator partner id.\n\nReturns:\n    SuccessResponse: The legacy create envelope carrying the new id.","operationId":"organizations-create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrganizationRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/":{"get":{"tags":["organizations"],"summary":"Get a single organization","description":"Return one organization by id (404 if not found or not authorized).\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    OrganizationResponse: The requested organization.","operationId":"organizations-get","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["organizations"],"summary":"Delete an organization","description":"Delete an organization on behalf of the integrator that manages it.\n\nRestricted to integrators, as in legacy. The organization must belong\nto the caller's own partner and must have granted that partner full\naccess (legacy's ``accept_impersonation``): another partner's or an\nunknown organization is a 404, one that withheld consent a 403.\nIts subscriptions are removed with it; its findings are kept, detached\nfrom the organization, as legacy left them.\n\nArgs:\n    organization_id (int): Identifier of the organization to delete.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    partner_id (int): The caller's own integrator partner id.\n\nReturns:\n    SuccessResponse: The legacy delete envelope carrying the id.","operationId":"organizations-delete","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/partner-access":{"put":{"tags":["organizations"],"summary":"Set the partner access level","description":"Set how much delegated access this organization grants its partner.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    body (PartnerAccessUpdateRequest): The new consent level.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"organizations-partner-access-update","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerAccessUpdateRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/domains/":{"get":{"tags":["organizations"],"summary":"List an organization's domains","description":"List the domains belonging to an organization.\n\nA 404 is returned when the organization is outside the caller's\nscope. Same documented shape and filters as ``GET /v1/domains/``,\nminus ``domain_id`` and ``organization_id`` (the path carries it).\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (OrganizationDomainListQuery): Filters + pagination.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    DomainListResponse: The organization's domains.","operationId":"organizations-domains-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact domain value (case-insensitive).","title":"Value"},"description":"Exact domain value (case-insensitive)."},{"name":"verified","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by verification status.","title":"Verified"},"description":"Filter by verification status."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"enum":["domain_checkbox","domain_dns"],"type":"string"},{"type":"null"}],"description":"Restrict to one proof method: `domain_dns` (TXT record) or `domain_checkbox` (self-attestation). Both when omitted.","title":"Kind"},"description":"Restrict to one proof method: `domain_dns` (TXT record) or `domain_checkbox` (self-attestation). Both when omitted."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/domains/{domain_id}":{"get":{"tags":["organizations"],"summary":"Get an organization's domain","description":"Return one domain belonging to an organization.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    domain_id (int): The domain's identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    DomainResponse: The requested domain.","operationId":"organizations-domains-get","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/events/":{"get":{"tags":["organizations"],"summary":"List an organization's pentests","description":"List pentests belonging to an organization.\n\nA 404 is returned when the organization is outside the caller's\nscope. Served at ``/pentests/`` (canonical) and ``/events/`` (a\ndeprecated legacy alias).\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PentestListResponse: The organization's pentests.","operationId":"organizations-events-list","deprecated":true,"parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/pentests/":{"get":{"tags":["organizations"],"summary":"List an organization's pentests","description":"List pentests belonging to an organization.\n\nA 404 is returned when the organization is outside the caller's\nscope. Served at ``/pentests/`` (canonical) and ``/events/`` (a\ndeprecated legacy alias).\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PentestListResponse: The organization's pentests.","operationId":"organizations-pentests-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/events/{pentest_slug}":{"get":{"tags":["organizations"],"summary":"Get an organization's pentest","description":"Return one pentest belonging to an organization.\n\nServed at ``/pentests/{pentest_slug}`` (canonical) and\n``/events/{pentest_slug}`` (a deprecated legacy alias).\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    pentest_slug (str): Slug identifying the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    PentestDetailResponse: The requested pentest.","operationId":"organizations-events-get","deprecated":true,"parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/pentests/{pentest_slug}":{"get":{"tags":["organizations"],"summary":"Get an organization's pentest","description":"Return one pentest belonging to an organization.\n\nServed at ``/pentests/{pentest_slug}`` (canonical) and\n``/events/{pentest_slug}`` (a deprecated legacy alias).\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    pentest_slug (str): Slug identifying the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    PentestDetailResponse: The requested pentest.","operationId":"organizations-pentests-get","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/assets/":{"get":{"tags":["organizations"],"summary":"List an organization's assets","description":"List assets belonging to an organization.\n\nA 404 is returned when the organization is outside the caller's\nscope.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    AssetListResponse: The organization's assets.","operationId":"organizations-assets-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/assets/{asset_id}":{"get":{"tags":["organizations"],"summary":"Get an organization's asset","description":"Return one asset belonging to an organization.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    asset_id (int): Primary key of the asset.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    AssetDetailResponse: The requested asset.","operationId":"organizations-assets-get","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/reports/":{"get":{"tags":["organizations"],"summary":"List an organization's reports","description":"List reports belonging to an organization.\n\nScoped to organization and partner integrators; a 404 is returned\nwhen the organization is outside the caller's scope.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    ReportListResponse: The organization's reports.","operationId":"organizations-reports-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}},{"name":"report_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Report Id"}},{"name":"pentest_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/reports/{report_id}":{"get":{"tags":["organizations"],"summary":"Get an organization's report","description":"Return one report belonging to an organization.\n\nScoped to organization and partner integrators; a 404 is returned\nwhen the organization or report is outside the caller's scope.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    report_id (int): Primary key of the report.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    ReportDetailResponse: The requested report.","operationId":"organizations-reports-get","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/visualizer_data":{"get":{"tags":["organizations"],"summary":"Get an organization's visualizer graph","description":"Return the asset/IP graph used by the organization visualizer.\n\nScoped to organization and partner integrators, and gated on the\n``visualizer`` entitlement for billing callers. Nodes and links are\nassembled from the organization's assets and their IP numbers;\n``pentest_slug`` narrows the node set to one pentest's scope in SQL,\nso a client does not have to page the scope endpoint and intersect.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    pentest_slug (str | None): Optional pentest scope filter.\n\nReturns:\n    VisualizerDataResponse: Graph nodes and links for the\n        organization.","operationId":"organizations-visualizer-data","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"pentest_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict the graph to the assets in this pentest's scope. Omit for the organization's whole surface.","title":"Pentest Slug"},"description":"Restrict the graph to the assets in this pentest's scope. Omit for the organization's whole surface."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisualizerDataResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/beacons/":{"post":{"tags":["organizations"],"summary":"Create an organization beacon","description":"Provision a beacon for an organization.\n\nScoped to organization and partner integrators. Reserves a rathole\nport and generates a WireGuard configuration; the response carries\nthe server configuration, which is returned exactly once here and\nnever persisted or retrievable again.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    body (CreateBeaconRequest): Beacon name, CIDRs, and optional\n        DNS/interface settings.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    BeaconCreatedResponse: The provisioned beacon and its one-time\n        WireGuard server config.","operationId":"organizations-beacons-create","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBeaconRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeaconCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["organizations"],"summary":"List an organization's beacons","description":"List beacons belonging to an organization.\n\nScoped to organization and partner integrators; supports filtering\nby status.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (OrganizationBeaconListQuery): Status filter + pagination.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    OrganizationBeaconListResponse: The organization's beacons.","operationId":"organizations-beacons-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/BeaconStatus"},{"type":"null"}],"description":"Status name (active, idle, faulty; any case) or numeric code (1, 2, 3).","title":"Status"},"description":"Status name (active, idle, faulty; any case) or numeric code (1, 2, 3)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationBeaconListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/beacons/{beacon_id}":{"get":{"tags":["organizations"],"summary":"Get an organization's beacon","description":"Return one beacon belonging to an organization.\n\nThe response includes the beacon's auth token and rathole\nparameters, so it also needs ``beacon:read_credentials``.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    beacon_id (int): Primary key of the beacon.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    BeaconDetailResponse: The requested beacon.","operationId":"organizations-beacons-get","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"beacon_id","in":"path","required":true,"schema":{"type":"integer","title":"Beacon Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeaconDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["organizations"],"summary":"Update an organization's beacon","description":"Update a beacon belonging to an organization.\n\nScoped to organization and partner integrators. Changes to the CIDR\nor DNS settings re-patch the stored client configurations.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    beacon_id (int): Primary key of the beacon.\n    body (UpdateBeaconRequest): Fields to change; omitted fields\n        are left unchanged.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    BeaconDetailResponse: The updated beacon.","operationId":"organizations-beacons-update","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"beacon_id","in":"path","required":true,"schema":{"type":"integer","title":"Beacon Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBeaconRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeaconDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["organizations"],"summary":"Delete an organization's beacon","description":"Delete a beacon belonging to an organization.\n\nReleases the reserved rathole port and removes the beacon's assets.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    beacon_id (int): Primary key of the beacon.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"organizations-beacons-delete","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"beacon_id","in":"path","required":true,"schema":{"type":"integer","title":"Beacon Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/beacons/{beacon_id}/test":{"post":{"tags":["organizations"],"summary":"Test an organization's beacon","description":"Check reachability of a target through an organization's beacon.\n\nScoped to organization and partner integrators.\n\nMatches the legacy ``api`` app's own behavior exactly: this is a\ndocumented placeholder (always reports reachable) pending a real\nWireGuard handshake / connectivity check — legacy never built one\neither (its view carries the identical TODO). The beacon is still\nresolved and scope-checked first, so an out-of-scope or unknown\nbeacon 404s rather than reporting a fake reachability result.\n\nArgs:\n    organization_id (int): Identifier of the organization.\n    beacon_id (int): Primary key of the beacon.\n    body (TestBeaconRequest): The ``host:port`` target to test.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    TestBeaconResponse: Always ``reachable=True`` for now.","operationId":"organizations-beacons-test","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"beacon_id","in":"path","required":true,"schema":{"type":"integer","title":"Beacon Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestBeaconRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestBeaconResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/invites/":{"get":{"tags":["organizations"],"summary":"List an organization's member invites","description":"List the organization's member invites (open and resolved).\n\nArgs:\n    organization_id (int): Identifier of the parent organization.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (OrganizationInviteListQuery): Filters + pagination.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    OrganizationInviteListResponse: The authorized, paginated invite list.","operationId":"organization-invites-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationInviteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["organizations"],"summary":"Invite a member to the organization","description":"Issue a member invite and send its email.\n\nArgs:\n    organization_id (int): Identifier of the parent organization.\n    body (CreateOrganizationInviteRequest): Invitee email + role.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    OrganizationInviteResponse: The issued invite (never the token).","operationId":"organization-invites-create","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrganizationInviteRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationInviteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/invites/{invite_id}":{"delete":{"tags":["organizations"],"summary":"Revoke a member invite","description":"Revoke an open member invite.\n\nArgs:\n    organization_id (int): Identifier of the parent organization.\n    invite_id (int): The invite to revoke.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"organization-invites-revoke","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"invite_id","in":"path","required":true,"schema":{"type":"integer","title":"Invite Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/invites/{invite_id}/resend":{"post":{"tags":["organizations"],"summary":"Re-send a member invite","description":"Rotate an invite's token and re-send its email.\n\nArgs:\n    organization_id (int): Identifier of the parent organization.\n    invite_id (int): The invite to re-send.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"organization-invites-resend","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"invite_id","in":"path","required":true,"schema":{"type":"integer","title":"Invite Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/domains":{"get":{"tags":["organizations"],"summary":"List an organization's identity domains","description":"List the domains and their proof state.\n\nReadable by every member of the organization, not only the owner: a domain\nand whether it is proven are configuration rather than a secret, and an admin\ndebugging why federated login admits nobody needs to see them.\n\nArgs:\n    organization_id (int): The parent organization.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n    query (IdentityDomainListQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    IdentityDomainsResponse: The domains. Empty is an ordinary answer -- an\n        organization that has not started proving one yet.","operationId":"organization-identity-domains-list","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityDomainsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["organizations"],"summary":"Register an identity domain to prove (owner)","description":"Register a domain and return the DNS record that proves it.\n\nThe domain is unproven until a check observes the record; nothing here can\nshortcut that. The response carries the token to publish, so a caller never\nneeds a second request to find out what to do next.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (IdentityDomainRequest): The domain to prove.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    IdentityDomainResponse: The registered domain and its challenge record.","operationId":"organization-identity-domains-create","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityDomainRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityDomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/domains/{domain_id}":{"delete":{"tags":["organizations"],"summary":"Withdraw an identity domain (owner)","description":"Withdraw a domain, proven or not.\n\nNarrows who federated login may admit, which is the safe direction -- but it\nalso stops new joiners at that domain, so it is deliberately the owner's\ndecision rather than something a wider role can do.\n\nArgs:\n    organization_id (int): The parent organization.\n    domain_id (int): The domain to withdraw.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nRaises:\n    NotFoundError: The organization is unknown or out of scope (from\n        ``_require_organization``, before any domain is read -- which is what\n        stops the route confirming that another organization exists), or the\n        id names no domain of *this* organization.\n    PermissionError: A member of this organization who may read its domains\n        but not withdraw them. 403 rather than 404 on purpose: they are\n        already known to be inside the organization, so there is nothing left\n        to disclose, and a 404 would read as \"that domain is gone\".","operationId":"organization-identity-domains-delete","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}},{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso":{"get":{"tags":["organizations"],"summary":"Read an organization's federated-login connection","description":"Read the connection.\n\nVisible to a platform admin and to the organization's owner. Analysts and\nviewers get 404 from the scoped SELECT — the facts that are genuinely public\n(``require_sso``, and an enabled connection's template id) already reach them\nthrough the unauthenticated discovery endpoint, so a grant here would add\nonly the provisioning policy.\n\n**Includes the per-template SAML endpoints**, which the customer has to paste\ninto their own IdP and which used to be returned only by the admin-only\n``POST|PUT .../sso/idp``. See :func:`_connection_response`: they are derived\nfrom a template id the reader can already see, they are published metadata\nZitadel serves unauthenticated, and no entitlement gates this read — an\norganization whose plan does not include SSO needs to be able to tell\n\"your plan does not cover this\" apart from a server fault.\n\nArgs:\n    organization_id (int): The parent organization.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    SsoConnectionResponse: The connection, plus the SAML URLs when it is\n        knowably SAML.","operationId":"organization-sso-read","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/template":{"put":{"tags":["organizations"],"summary":"Point an organization at its Zitadel IdP template (admin)","description":"Create the connection, or re-point an existing one.\n\nIdempotent by organization: there is at most one connection per\norganization, so a repeat call points the existing row rather than inserting\na duplicate. The branch is chosen from the caller's *scoped* read, so a\ncaller who cannot see the existing row falls to the create arm and is\nrefused there -- by their own lack of the verb, or by the conflict on the\norganization slot.\n\nThrough the workflow rather than the connection service: naming a template\nid means reading that template back from Zitadel to derive the EntityID\nstored alongside it, which an entity service may not do. See\n:meth:`~phoenix.services.workflows.users.sso_idp_template.SsoIdpTemplateWorkflow.point_at_template`\nfor what a connection pointed at a SAML template without one does.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoTemplateRequest): Slug and template id.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    SsoConnectionResponse: The connection as it now stands.","operationId":"organization-sso-set-template","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/policy":{"put":{"tags":["organizations"],"summary":"Set an organization's federated-login policy (owner)","description":"Set whether federated login is served and whether it is mandatory.\n\nThe body is total, so this replaces the policy rather than patching it. The\nwrite interlocks are pre-flighted by the service and answer 400: enabling a\nconnection no admin has pointed anywhere, and mandating SSO on one that is\nnot served -- the latter an organization-wide lockout, because members of an\nSSO-mandated organization hold no password. The CHECK constraints behind\nthem remain the floor, but reaching one would surface as an unmapped 500.\n\nTurning ``require_sso`` on adds one more precondition, enforced by the\nworkflow because it needs Zitadel: every member must already be linked to\nthe IdP, or the mandate would lock the unlinked ones out on their next\nrequest. Arming with unlinked members answers 409; link them first.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoPolicyRequest): The policy fields to change.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    SsoConnectionResponse: The connection as it now stands.","operationId":"organization-sso-set-policy","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoPolicyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/provisioning":{"put":{"tags":["organizations"],"summary":"Arm or disarm just-in-time provisioning (owner)","description":"Arm or disarm minting accounts on a first federated login.\n\nRouted through :class:`SsoProvisioningWorkflow` rather than writing the field,\nwhich is the whole point of the separate sub-resource: arming requires a\ndomain proven by DNS *and* re-confirmed within the proof window, and a plain\nfield write would bypass that. Arming without one answers 400, as does arming\na connection that is not served -- the ``provisioning_requires_enabled``\ninterlock, pre-flighted so it never reaches the CHECK behind it.\n\nIts own route rather than fields on ``PUT .../sso/policy`` for the same\nreason — see :class:`SsoProvisioningRequest`.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoProvisioningRequest): Whether to arm, and at which role.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    SsoConnectionResponse: The connection as it now stands.","operationId":"organization-sso-set-provisioning","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoProvisioningRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/test":{"post":{"tags":["organizations"],"summary":"Start a federated-login configuration test (owner)","description":"Hand back a real login ceremony to run against the customer's own IdP.\n\nThe safety net under ``PUT .../sso/policy``: every interlock there proves\nthe configuration is *coherent*, and none of them proves the customer's IdP\nanswers. An owner who turns on ``require_sso`` against a connection that\ndoes not work locks out an organization whose members hold no password.\n\n``POST`` because it creates an intent in Zitadel, and no body because the\nonly input is the connection, which the path already names. Answers 400 for\na connection no admin has pointed anywhere, and 503 when this deployment\nconfigured no return URLs.\n\nArgs:\n    organization_id (int): The parent organization.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    SsoTestStartResponse: A redirect URL or a form to POST, depending on the\n        provider's binding.","operationId":"organization-sso-test-start","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoTestStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/test/result":{"post":{"tags":["organizations"],"summary":"Read the verdict of a configuration test (owner)","description":"Redeem the ceremony's intent and report what a member's login would hit.\n\n``POST`` on a sub-resource despite being a read: the intent token is a\nbearer credential, and Zitadel already exposes it in a browser URL -- it\nmust not travel in this service's request line as well.\n\nAuthenticates nobody -- no session, no account, no IdP link. Writes one\nthing, and only on a pass: ``verified_at`` / ``verified_template_id``, which\nis what ``require_sso`` is gated on. An intent naming a template other than\nthis connection's is refused rather than reported, so a redeemable intent\nobtained elsewhere cannot make a broken connection look tested.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoTestResultRequest): The intent id and token from the return\n        URL.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    SsoTestVerdictResponse: The verdict and the values it compared.","operationId":"organization-sso-test-result","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoTestResultRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoTestVerdictResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/idp":{"post":{"tags":["organizations"],"summary":"Mint an organization's Zitadel IdP template (admin)","description":"Create the customer's IdP template in Zitadel and point the connection.\n\nReplaces the console step onboarding used to need. The connection must\nalready exist -- create it with ``PUT .../sso/template`` first, which is\nwhere ``login_slug`` is chosen.\n\n**For SAML the response is part of the deliverable.** It carries the ACS,\nmetadata, certificate and SLO URLs unique to this template; the customer\nconfigures their IdP against those, and no shared endpoint substitutes.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoIdpRequest): Provider configuration, discriminated on\n        ``protocol``.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    IdpTemplateResponse: The template id and, for SAML, the customer's URLs.","operationId":"organization-sso-create-idp","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OidcTemplateRequest"},{"$ref":"#/components/schemas/SamlTemplateRequest"}],"discriminator":{"propertyName":"protocol","mapping":{"oidc":"#/components/schemas/OidcTemplateRequest","saml":"#/components/schemas/SamlTemplateRequest"}},"title":"Payload"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdpTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["organizations"],"summary":"Update an organization's Zitadel IdP template (admin)","description":"Update the template in place -- new issuer, rotated secret, new metadata.\n\nThe template id does not change, so the customer's ACS URL stays valid and\nconnections referencing it are unaffected. A full replacement, not a patch,\nwith one allowance: an OIDC body omitting ``client_secret`` keeps the stored\none.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoIdpRequest): The new configuration.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    IdpTemplateResponse: The template id and, for SAML, the customer's URLs.","operationId":"organization-sso-update-idp","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OidcTemplateRequest"},{"$ref":"#/components/schemas/SamlTemplateRequest"}],"discriminator":{"propertyName":"protocol","mapping":{"oidc":"#/components/schemas/OidcTemplateRequest","saml":"#/components/schemas/SamlTemplateRequest"}},"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdpTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/idp/self":{"post":{"tags":["organizations"],"summary":"Set up federated login from your own IdP (owner)","description":"Mint the owner's own IdP template in one call -- no admin, no slug to pick.\n\nThe self-service counterpart to the admin's two-step (``PUT .../sso/template``\nthen ``POST .../sso/idp``): :meth:`OwnerSsoProvisioningWorkflow.establish`\nderives the slug from the organization name, creates the connection disabled\nand unverified, and mints the template. SSO is a paid feature, so an\norganization whose plan lacks it is refused (402) before any Zitadel call.\nFirst-time setup only -- an organization already pointed at a template is\nrefused (409); changing a live one is ``PUT .../sso/idp/self``.\n\n**For SAML the response is part of the deliverable** -- the ACS, metadata,\ncertificate and SLO URLs unique to this template, which the customer\nconfigures their IdP against.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoIdpRequest): Provider configuration, discriminated on\n        ``protocol``.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    IdpTemplateResponse: The template id and, for SAML, the customer's URLs.","operationId":"organization-sso-establish-self","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OidcTemplateRequest"},{"$ref":"#/components/schemas/SamlTemplateRequest"}],"discriminator":{"propertyName":"protocol","mapping":{"oidc":"#/components/schemas/OidcTemplateRequest","saml":"#/components/schemas/SamlTemplateRequest"}},"title":"Payload"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdpTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["organizations"],"summary":"Replace your federated-login IdP configuration (owner)","description":"Update the owner's template in place -- new metadata, rotated secret.\n\nThe template id is preserved, so the customer's ACS URL stays valid.\n\nDeliberately **not** entitlement-gated: this repairs an existing, possibly\nSSO-mandated login path, and members of an SSO-mandated organization hold no\npassword. Gating repair on a lapsed plan would turn a billing gap into a\nlockout. The paywall is on ``establish``, where the connection is created.\n\nArgs:\n    organization_id (int): The parent organization.\n    payload (SsoIdpRequest): The new configuration.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    IdpTemplateResponse: The template id and, for SAML, the customer's URLs.","operationId":"organization-sso-replace-self","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OidcTemplateRequest"},{"$ref":"#/components/schemas/SamlTemplateRequest"}],"discriminator":{"propertyName":"protocol","mapping":{"oidc":"#/components/schemas/OidcTemplateRequest","saml":"#/components/schemas/SamlTemplateRequest"}},"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdpTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/organizations/{organization_id}/sso/members/link":{"post":{"tags":["organizations"],"summary":"Link your organization's members to its IdP (owner)","description":"Pre-create the IdP links the organization's members need for SSO.\n\nThe self-service equivalent of the migration CLI ``link-sso-members``: an\nowner links their own members so that arming ``require_sso`` does not lock\nthe unlinked ones out. Idempotent -- already-linked members report as such --\nso it is safe to re-run after fixing whatever stopped one.\n\nArgs:\n    organization_id (int): The parent organization.\n    db (AsyncSession): Request-scoped session.\n    context (ActiveContext): Resolved caller.\n\nReturns:\n    dict[str, object]: How many were linked, already linked, and still need\n        attention with a reason for each of the last, serialized through\n        ``response_model=MemberLinkSummaryResponse``.","operationId":"organization-sso-link-members","parameters":[{"name":"organization_id","in":"path","required":true,"schema":{"type":"integer","title":"Organization Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberLinkSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/invites/partner/":{"get":{"tags":["invites"],"summary":"List partner invites","description":"List partner invites visible to the caller, filtered + paginated.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PartnerInviteListQuery): Email/partner filters + paging.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PartnerInviteListResponse: The authorized, paginated invite list.","operationId":"partner-invites-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}},{"name":"partner_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Partner Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerInviteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["invites"],"summary":"Issue a partner invite","description":"Issue a partner invite and send its email.\n\nArgs:\n    body (CreatePartnerInviteRequest): Invitee email, partner, role.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    PartnerInviteResponse: The issued invite (never the token).","operationId":"partner-invites-create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePartnerInviteRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerInviteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/invites/partner/{invite_id}":{"delete":{"tags":["invites"],"summary":"Revoke a partner invite","description":"Revoke an open partner invite.\n\nArgs:\n    invite_id (int): The invite to revoke.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"partner-invites-revoke","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"integer","title":"Invite Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/invites/partner/{invite_id}/resend":{"post":{"tags":["invites"],"summary":"Re-send a partner invite","description":"Rotate an invite's token and re-send its email.\n\nArgs:\n    invite_id (int): The invite to re-send.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"partner-invites-resend","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"integer","title":"Invite Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/invites/triager/":{"get":{"tags":["invites"],"summary":"List triager invites","description":"List triager invites visible to the caller, filtered + paginated.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (TriagerInviteListQuery): Email filter + pagination.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    TriagerInviteListResponse: The authorized, paginated invite list.","operationId":"triager-invites-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriagerInviteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["invites"],"summary":"Issue a triager invite","description":"Issue a triager invite and send its email.\n\nArgs:\n    body (CreateTriagerInviteRequest): The invitee email.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    TriagerInviteResponse: The issued invite (never the token).","operationId":"triager-invites-create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTriagerInviteRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriagerInviteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/invites/triager/{invite_id}":{"delete":{"tags":["invites"],"summary":"Revoke a triager invite","description":"Revoke an open triager invite.\n\nArgs:\n    invite_id (int): The invite to revoke.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"triager-invites-revoke","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"integer","title":"Invite Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/invites/triager/{invite_id}/resend":{"post":{"tags":["invites"],"summary":"Re-send a triager invite","description":"Rotate an invite's token and re-send its email.\n\nArgs:\n    invite_id (int): The invite to re-send.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.","operationId":"triager-invites-resend","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"integer","title":"Invite Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reports/":{"get":{"tags":["reports"],"summary":"List reports","description":"List reports visible to the caller, filtered and paginated.\n\nScoped to the caller's integrator access (organization, partner) by\n``DocumentService``, which also gates the report feature.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (ReportListQuery): Filters + pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    ReportListResponse: The authorized, paginated report list.","operationId":"reports-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}},{"name":"report_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Report Id"}},{"name":"pentest_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reports/{report_id}/":{"get":{"tags":["reports"],"summary":"Get a single report","description":"Return one report by id (404 if not found or not authorized).\n\nAccess is restricted to integrators (organization, partner) for\norganizations with the report feature enabled.\n\nArgs:\n    report_id (int): Primary key of the report.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    ReportDetailResponse: The requested report.","operationId":"reports-get","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reports/{report_id}/download":{"get":{"tags":["reports"],"summary":"Download a report","description":"Redirect to a time-limited signed URL for a report.\n\nThe signed URL is minted per call and never persisted, so the\nredirect is the response rather than a body carrying the link.\nRestricted to integrators (organization, partner) with the report\nfeature enabled.\n\nA report still being generated answers ``202`` and the caller polls;\none whose generation failed answers ``400``.\n\nArgs:\n    report_id (int): Primary key of the report.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    Response: A ``302`` to the signed URL, or a ``202`` body while\n        the artifact is pending. Returned directly rather than\n        through a ``response_model`` because the two outcomes carry\n        different status codes.","operationId":"reports-download","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}}],"responses":{"302":{"description":"Redirect to a short-lived signed download URL."},"202":{"description":"The report is still being generated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportPendingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/settings/default-event-duration/":{"get":{"tags":["settings"],"summary":"Get the partner's default pentest duration (deprecated path)","description":"Return the default pentest duration for the caller's partner.\n\nRestricted to integrators (the ``get_integrator_partner_id`` dependency\nresolves and validates the owning partner). The value comes from the\npartner's active subscription.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    partner_id (int): The integrator's resolved partner id.\n\nReturns:\n    DefaultPentestDurationResponse: The configured default duration and\n        whether the integrator may change it.","operationId":"settings-default-event-duration-list","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DefaultPentestDurationResponse"}}}}},"deprecated":true},"put":{"tags":["settings"],"summary":"Update the partner's default pentest duration (deprecated path)","description":"Set the default pentest duration for the caller's partner.\n\nRestricted to integrators whose subscription has the\n``integrators_events_duration_editable`` feature (a 402 otherwise); the\nfeature gate and the Cerbos ``update_pentest_duration`` write are\nenforced by the service. The feature key keeps its pre-rename spelling\nbecause it is a row in the feature catalogue that legacy apps read —\ntracked in ethiack/phoenix#310.\n\nAnswers the write envelope, not the updated setting: a caller that\nneeds the stored value reads it back from the GET. ``id`` is omitted\n(``response_model_exclude_none``) because the setting is a singleton\non the subscription and has none to name.\n\nArgs:\n    body (UpdateDefaultPentestDurationRequest): New default duration.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    partner_id (int): The integrator's resolved partner id.\n\nReturns:\n    SuccessResponse: Confirmation naming the duration now stored.","operationId":"settings-default-event-duration-update","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDefaultPentestDurationRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"deprecated":true}},"/v1/settings/default-pentest-duration/":{"get":{"tags":["settings"],"summary":"Get the partner's default pentest duration","description":"Return the default pentest duration for the caller's partner.\n\nRestricted to integrators (the ``get_integrator_partner_id`` dependency\nresolves and validates the owning partner). The value comes from the\npartner's active subscription.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    partner_id (int): The integrator's resolved partner id.\n\nReturns:\n    DefaultPentestDurationResponse: The configured default duration and\n        whether the integrator may change it.","operationId":"settings-default-pentest-duration-list","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DefaultPentestDurationResponse"}}}}}},"put":{"tags":["settings"],"summary":"Update the partner's default pentest duration","description":"Set the default pentest duration for the caller's partner.\n\nRestricted to integrators whose subscription has the\n``integrators_events_duration_editable`` feature (a 402 otherwise); the\nfeature gate and the Cerbos ``update_pentest_duration`` write are\nenforced by the service. The feature key keeps its pre-rename spelling\nbecause it is a row in the feature catalogue that legacy apps read —\ntracked in ethiack/phoenix#310.\n\nAnswers the write envelope, not the updated setting: a caller that\nneeds the stored value reads it back from the GET. ``id`` is omitted\n(``response_model_exclude_none``) because the setting is a singleton\non the subscription and has none to name.\n\nArgs:\n    body (UpdateDefaultPentestDurationRequest): New default duration.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    partner_id (int): The integrator's resolved partner id.\n\nReturns:\n    SuccessResponse: Confirmation naming the duration now stored.","operationId":"settings-default-pentest-duration-update","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDefaultPentestDurationRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/audit-logs/":{"get":{"tags":["audit-logs"],"summary":"List audit log entries","description":"List audit log entries visible to the caller, filtered and paginated.\n\nAuthorization is org-scoped by ``AuditLogService`` (org members see\ntheir own organization's entries). ``total`` reflects the same scope\nand filters as the returned page. The page and its total are read\nunder one authorization plan (``get_page``) rather than two separate\n``get`` / ``count`` calls.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (AuditLogListQuery): Filters + pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    AuditLogListResponse: The authorized, paginated audit log list.","operationId":"audit-logs-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}},{"name":"operation","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operation"}},{"name":"operation_category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operation Category"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"actor_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Id"}},{"name":"actor_kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Kind"}},{"name":"resource_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource Type"}},{"name":"resource_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLogListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/":{"get":{"tags":["pentests"],"summary":"List pentests","description":"List pentests visible to the caller.\n\nScoped to the caller's pentest memberships by ``PentestService``;\nthe route only translates transport.\n\nArgs:\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PentestListQuery): Filters + pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PentestListResponse: The authorized, paginated pentest list.","operationId":"pentests-list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"organization_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"}},{"name":"pentest_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/PentestType"},{"type":"null"}],"title":"Pentest Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/PentestStatus"},{"type":"null"}],"title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"pentest_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"}},{"name":"running","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Running"}},{"name":"finished","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Finished"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["pentests","pentests"],"summary":"Create a machine pentest","description":"Create a machine pentest, in draft.\n\nConflicts (HTTP 409) when the organization is already running as many\ncontinuous pentests as its subscription meters. The pentest is left\nin draft, as the pre-rename endpoint left it: ``POST\n{slug}/start`` is what puts it to work.\n\nArgs:\n    body (CreateMachinePentestRequest): Owning organization and the\n        seeding options.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename create envelope, carrying the new\n        pentest's **slug** as ``id`` — that is what the endpoint\n        answered, and a client stores what it is given.","operationId":"pentests-create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMachinePentestRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/":{"get":{"tags":["pentests","pentests"],"summary":"Get a pentest","description":"Return one pentest by slug (404 if not found or not authorized).\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    PentestDetailResponse: The requested pentest.","operationId":"pentests-get","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/start":{"post":{"tags":["pentests","pentests"],"summary":"Start a pentest","description":"Put a continuous pentest to work, from draft or from paused.\n\nOne endpoint for both, as the pre-rename one was: a client that\ncreates a pentest and starts it cannot be asked which of the two\ntransitions it needs. A draft is published (DRAFT → DONE); a paused\npentest is resumed (PAUSED → DONE). Either way the computed status\nbecomes RUNNING, and the recon rides the staged domain event rather\nthan this request.\n\nBoth branches re-verify the asset cap and the continuous-test\nentitlement and cap, so a pause is not a way around them.\n\nCONTINUOUS only; a wrong type or an unstartable config raises (400).\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename lifecycle envelope.\n\nRaises:\n    ValueError: The pentest is not CONTINUOUS.\n    ConflictError: An asset or continuous-test cap is exceeded (409).\n    FeatureNotEntitledError: The plan no longer meters continuous\n        tests (402).","operationId":"pentests-start","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/stop":{"post":{"tags":["pentests","pentests"],"summary":"Stop a pentest","description":"Stop (pause) a running continuous pentest.\n\nPauses a RUNNING continuous pentest (config → PAUSED). The actual recon\nhalt rides the staged ``PentestPaused`` domain event (handler-deferred).\nCONTINUOUS only; a wrong type, or a pentest that is not running (a draft\nor already paused), raises (400).\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename lifecycle envelope.","operationId":"pentests-stop","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/status":{"get":{"tags":["pentests","pentests"],"summary":"Get pentest status","description":"Return the pentest's aggregated run status.\n\nThe status is computed from the pentest's config and dates, so this is\na plain authorized read.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    PentestResponse: The pentest with its current status.","operationId":"pentests-status","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/findings/":{"get":{"tags":["pentests","pentests"],"summary":"List pentest findings","description":"List the findings reported for a pentest.\n\nVisible to any authenticated caller authorized for the pentest;\npartners restricted to organizations they actively impersonate.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    FindingListResponse: The authorized, paginated finding list.","operationId":"pentests-findings-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindingListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["pentests","pentests"],"summary":"Submit a pentest finding","description":"Submit a new finding against a pentest.\n\nOpen to hackers and machine integrators contributing results to\nthe pentest. The submission is recorded together with its\nreporting hacker association.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    body (SubmitFindingRequest): The finding submission payload.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SubmitFindingResponse: The identifier of the submitted finding.","operationId":"pentests-findings-create","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitFindingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitFindingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/findings/{finding_id}":{"get":{"tags":["pentests","pentests"],"summary":"Get a pentest finding","description":"Return one pentest finding (404 if not found or not authorized).\n\nVisible to any authenticated caller authorized for the pentest;\npartners restricted to organizations they actively impersonate.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    finding_id (int): Identifier of the finding within the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    FindingDetailResponse: The requested finding.","operationId":"pentests-findings-get","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindingDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/findings/attachments":{"post":{"tags":["pentests","pentests"],"summary":"Upload a finding attachment","description":"Store a file the caller will reference from a later submission.\n\nReserved for machine integrators. The attachment is stored with no\nfinding: the engine uploads before the finding exists and lists the\nreturned id in ``SubmitFindingRequest.attachments``, which the ingest\nthen claims onto the created row.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    attachment (UploadFile): The multipart file part.\n\nReturns:\n    FindingAttachmentUploadResponse: The stored attachment's id.","operationId":"pentests-findings-attachments","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_pentests-findings-attachments"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindingAttachmentUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/findings/{finding_id}/retest/":{"get":{"tags":["pentests","pentests"],"summary":"Get finding retest status","description":"Return retest eligibility and the latest retest job for a finding.\n\nVisible to any authenticated caller authorized for the pentest.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    finding_id (int): Identifier of the finding within the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    RetestInfoResponse: The finding's retest eligibility and status.","operationId":"pentests-findings-retest-info","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetestInfoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["pentests","pentests"],"summary":"Start a finding retest","description":"Launch a retest job for a finding.\n\nConflicts (HTTP 409) when a retest is already in progress or the\nfinding is not re-testable, and is rate limited (HTTP 429).\nAvailable to any authenticated caller authorized for the pentest.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    finding_id (int): Identifier of the finding within the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename envelope, naming the retest\n        job by its uuid rather than the finding.","operationId":"pentests-findings-retest-start","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"finding_id","in":"path","required":true,"schema":{"type":"integer","title":"Finding Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/scope/":{"get":{"tags":["pentests","pentests"],"summary":"List pentest scope","description":"Return the pentest's in- and out-of-scope assets.\n\nReturns the full in- and out-of-scope collections (use\n``/scope/assets/`` for paginated access). Beacon details for the\nin-scope assets are included only for machine callers. An in-scope\nasset with an uploaded app bundle (APK / IPA) carries a short-lived\nsigned ``attachment_url`` to download it.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PentestScopeQuery): Pagination query params, applied to\n        each collection independently.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PentestScopeResponse: The pentest scope split into in- and\n        out-of-scope assets.","operationId":"pentests-scope-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestScopeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["pentests","pentests"],"summary":"Update pentest scope","description":"Bulk-move assets between in- and out-of-scope.\n\nRestricted to organization and partner integrators.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    body (PatchPentestScopeRequest): Assets to move in or out of\n        scope.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename update envelope, which names\n        no id -- the PATCH edits the collection, not a row.","operationId":"pentests-scope-update","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchPentestScopeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/scope/assets/":{"get":{"tags":["pentests","pentests"],"summary":"List pentest scope assets","description":"List the pentest's scope assets, filterable by scope membership.\n\nSupports filtering by in-scope status and asset identifier.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PentestAssetQuery): Pagination + optional in_scope/asset_id.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PentestAssetListResponse: The authorized, paginated scope\n        asset list.","operationId":"pentests-scope-assets-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"in_scope","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In Scope"}},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestAssetListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["pentests","pentests"],"summary":"Add a pentest scope asset","description":"Add an asset to the pentest scope.\n\nRestricted to organization and partner integrators.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    body (AddPentestAssetRequest): Asset to add to the scope.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy create envelope carrying the new id.","operationId":"pentests-scope-assets-create","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddPentestAssetRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/scope/assets/{asset_id}":{"get":{"tags":["pentests","pentests"],"summary":"Get a pentest scope asset","description":"Return one scope asset (404 if not found or not authorized).\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    asset_id (int): Identifier of the scope asset (the asset id).\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    PentestAssetResponse: The requested scope asset.","operationId":"pentests-scope-assets-get","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["pentests","pentests"],"summary":"Update a pentest scope asset","description":"Update one scope asset's attributes.\n\nRestricted to organization and partner integrators.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    asset_id (int): Identifier of the scope asset.\n    body (PatchPentestAssetRequest): Fields to update on the scope\n        asset.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The pre-rename update envelope.","operationId":"pentests-scope-assets-update","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchPentestAssetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["pentests","pentests"],"summary":"Remove a pentest scope asset","description":"Remove an asset from the pentest scope.\n\nRestricted to organization and partner integrators.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    asset_id (int): Identifier of the scope asset.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy delete envelope, echoing the removed id.","operationId":"pentests-scope-assets-delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/scope/urls/":{"get":{"tags":["pentests","pentests"],"summary":"List pentest scope URLs","description":"List the distinct URLs of the pentest's scoped assets.\n\nAvailable to any caller authorized for the pentest's scope (not\nmachine-only, unlike the ``/urls`` listing).\n\nServes the in-scope half unless the gates say otherwise (see\n:class:`PentestScopeUrlQuery`); wanting neither half is an empty\npage, not a query that cannot match.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PentestScopeUrlQuery): Pagination + the two scope gates.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    PentestScopeUrlListResponse: The authorized, paginated scope\n        URL list.","operationId":"pentests-scope-urls-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"in_scope","in":"query","required":false,"schema":{"type":"boolean","description":"Include URLs of the pentest's in-scope assets.","default":true,"title":"In Scope"},"description":"Include URLs of the pentest's in-scope assets."},{"name":"out_of_scope","in":"query","required":false,"schema":{"type":"boolean","description":"Include URLs of the pentest's out-of-scope assets.","default":false,"title":"Out Of Scope"},"description":"Include URLs of the pentest's out-of-scope assets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PentestScopeUrlListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/reports/":{"get":{"tags":["pentests","pentests"],"summary":"List pentest reports","description":"List the reports generated for a pentest.\n\nVisible to organization and partner integrators authorized for the\npentest.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    ReportListResponse: The authorized pentest report list.","operationId":"pentests-reports-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["pentests","pentests"],"summary":"Generate a pentest report","description":"Request asynchronous generation of a pentest report.\n\nAccepted for processing and generated in the background; the\nreturned report reflects its initial, pending state. Restricted to\norganization and partner integrators authorized for the pentest.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    body (CreateReportRequest): Report generation parameters.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    ReportResponse: The accepted report and its current state.","operationId":"pentests-reports-create","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReportRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/reports/{report_id}":{"get":{"tags":["pentests","pentests"],"summary":"Get a pentest report","description":"Return one pentest report by id (404 if not found or not authorized).\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    report_id (int): Identifier of the report within the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    ReportDetailResponse: The requested report.","operationId":"pentests-reports-get","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/reports/{report_id}/download":{"get":{"tags":["pentests","pentests"],"summary":"Download a pentest report","description":"Redirect to a time-limited signed URL for a pentest's report.\n\nThe signed URL is minted per call and never persisted, so the\nredirect is the response rather than a body carrying the link.\nLimited to organization and partner integrators authorized for the\npentest.\n\nA report still being generated answers ``202`` and the caller polls;\none whose generation failed answers ``400``.\n\nThe pending body omits the null ``success`` key that the unnested\n``/v1/reports`` download includes. The two have always differed\nthere, and a caller reading the key on one would break if it were\ndropped to match the other.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    report_id (int): Identifier of the report within the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    Response: A ``302`` to the signed URL, or a ``202`` body while\n        the artifact is pending. Returned directly rather than\n        through a ``response_model`` because the two outcomes carry\n        different status codes.","operationId":"pentests-reports-download","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}}],"responses":{"302":{"description":"Redirect to a short-lived signed download URL."},"202":{"description":"The report is still being generated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportPendingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/recons/":{"get":{"tags":["pentests","pentests"],"summary":"List recon launches","description":"List reconnaissance launches for a pentest.\n\nRows are Cerbos-scoped by ``LaunchService``: members of the owning\norganization, integrators of its partner, and a partner operating it\nall see its recon launches; nobody sees another organization's.\n\nA row in PENDING or IN_PROGRESS means a recon is still occupying the\npentest — PENDING included, since a claim holds the slot from the\nmoment it is written, before the engine's first callback.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (PaginationQuery): Pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    ReconListResponse: The authorized recon launches for the pentest.","operationId":"pentests-recons-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["pentests","pentests"],"summary":"Launch a recon","description":"Launch a reconnaissance job for a pentest.\n\nThe launch is attributed to the dedicated recon machine user, not to\nthe caller — the requester's identity is recorded in the audit trail,\nnot on the launch row.\n\n201 means the recon has been **scheduled**, not that artiacker-manager\nhas accepted it: the row is claimed here and POSTed by the dispatch task\nthe claim triggers, so the response carries a ``PENDING`` launch whose\nstatus is the thing to read afterwards. A cluster that later declines it\nis recorded as ``ERROR`` on that launch, not returned here.\n\nEvery failure maps through the centralized handlers, so nothing is\ncaught here: an unknown or out-of-scope slug is 404, a Cerbos denial\n403, no wildcard in scope 400, an already-running recon 409, and an\nunreachable artiacker-manager — for the already-running check, the one\ncall still made inline — 500.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    SuccessResponse: The legacy create envelope carrying the launch id.","operationId":"pentests-recons-create","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/recons/{recon_id}":{"get":{"tags":["pentests","pentests"],"summary":"Get a recon launch","description":"Return one recon launch by id (404 if not found or not authorized).\n\nRestricted to integrators of the owning organization or partner.\n\nArgs:\n    pentest_slug (str): Public pentest identifier.\n    recon_id (int): Identifier of the recon launch.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    ReconResponse: The requested recon launch.","operationId":"pentests-recons-get","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"recon_id","in":"path","required":true,"schema":{"type":"integer","title":"Recon Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/launches/":{"get":{"tags":["pentests","pentests"],"summary":"List pentest launches","description":"List the launches dispatched for a pentest.\n\nRestricted to machine callers operating within the pentest's scope.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n    query (LaunchListQuery): Filters + pagination query params.\n    pager (Paginator): Fills the response's navigation metadata.\n\nReturns:\n    LaunchListResponse: The authorized launches for the pentest.","operationId":"pentests-launches-list","parameters":[{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX).","default":25,"title":"Per Page"},"description":"Page size. Values above 500 are clamped to it (or rejected, per REST__PAGINATION__PER_PAGE_OVER_MAX)."},{"name":"mode","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/pentests/{pentest_slug}/launches/{launch_id}":{"get":{"tags":["pentests","pentests"],"summary":"Get a pentest launch","description":"Return one launch by id (404 if not found or not authorized).\n\nRestricted to machine callers operating within the pentest's scope.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    launch_id (int): Identifier of the launch within the pentest.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    LaunchResponse: The requested launch.","operationId":"pentests-launches-get","parameters":[{"name":"launch_id","in":"path","required":true,"schema":{"type":"integer","title":"Launch Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["pentests","pentests"],"summary":"Update a pentest launch","description":"Update a launch's mutable fields.\n\nRestricted to machine callers operating within the pentest's scope.\n\nArgs:\n    pentest_id (int): The parent pentest, resolved and access-checked\n        by the router's gate.\n    launch_id (int): Identifier of the launch within the pentest.\n    body (UpdateLaunchRequest): Fields to update on the launch.\n    db (AsyncSession): Request-scoped database session.\n    context (ActiveContext): Resolved caller identity and scope.\n\nReturns:\n    LaunchResponse: The updated launch.","operationId":"pentests-launches-update","parameters":[{"name":"launch_id","in":"path","required":true,"schema":{"type":"integer","title":"Launch Id"}},{"name":"pentest_slug","in":"path","required":true,"schema":{"type":"string","title":"Pentest Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLaunchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/":{"get":{"tags":["general"],"summary":"API version probe","description":"Return the running API version.\n\nThe only unauthenticated endpoint; used by clients and\nintegrators as a liveness/version probe.\n\nReturns:\n    dict[str, str]: A single ``version`` entry.","operationId":"general-version","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response General-Version"}}}}}}}},"components":{"schemas":{"AddPentestAssetRequest":{"properties":{"asset_id":{"type":"integer","title":"Asset Id"},"in_scope":{"type":"boolean","title":"In Scope","default":false},"wildcard":{"type":"boolean","title":"Wildcard","default":false},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["asset_id"],"title":"AddPentestAssetRequest","description":"Request body for adding an asset to a pentest's scope.\n\nAttributes:\n    asset_id (int): ID of the asset to add.\n    in_scope (bool): Whether the asset is in scope.\n    wildcard (bool): Enable wildcard matching.\n    description (str | None): Optional description."},"AssetDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"value":{"type":"string","title":"Value"},"type":{"type":"string","title":"Type"},"tier":{"anyOf":[{"$ref":"#/components/schemas/AssetTier"},{"type":"null"}]},"organization_id":{"type":"integer","title":"Organization Id"},"private":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Private"},"created":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created"},"ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip","description":"The asset's resolved IP address, if one is known."},"risk_score":{"type":"string","title":"Risk Score","description":"Highest actionable severity: `high`, `medium` or `low`.","default":"low"},"services":{"items":{"type":"string"},"type":"array","title":"Services","description":"Service names discovered on the asset, deduplicated."},"ports":{"items":{"type":"integer"},"type":"array","title":"Ports","description":"Ports the asset's services listen on, deduplicated."},"technologies":{"items":{"$ref":"#/components/schemas/TechnologyResponse"},"type":"array","title":"Technologies","description":"Technologies detected across the asset's services, deduplicated by id."},"organization_name":{"type":"string","title":"Organization Name","description":"Name of the organization that owns the asset.","default":""},"tld":{"type":"boolean","title":"Tld","description":"Whether the asset's value is itself a domain the owning organization has proven, via an active verification.","default":false},"n_findings":{"type":"integer","title":"N Findings","description":"How many of the asset's findings are open or triaged.","default":0},"findings":{"$ref":"#/components/schemas/AssetFindingsResponse","description":"The asset's open and triaged findings. Resolved ones -- fixed, invalid, duplicate, accepted -- are not included."}},"type":"object","required":["id","value","type","organization_id"],"title":"AssetDetailResponse","description":"Detailed asset representation (single-item view).\n\nExtends :class:`AssetResponse` with the timestamps, the resolved IP\naddress, the asset's risk band, and the services\ndiscovered on it — everything the asset detail panel renders, in one\ncall.\n\nAttributes:\n    created (datetime.datetime | None): Creation timestamp.\n    risk_score (str): The asset's risk band — ``high`` when any open\n        or triaged finding is high severity or above (critical and\n        cosmic fold in), ``medium`` when any is medium, else ``low``.\n        Filled by the route from :meth:`AssetService.risk_scores`.\n    ip (str | None): The asset's resolved IP address, or ``null``.\n    services (list[str]): Names of the services discovered on the\n        asset, deduplicated. Their ports, protocols and technologies\n        are served by ``GET /v1/assets/{id}/services``.\n    ports (list[int]): The distinct ports those services listen on."},"AssetFindingsResponse":{"properties":{"findings":{"items":{"$ref":"#/components/schemas/FindingResponse"},"type":"array","title":"Findings","description":"The asset's open and triaged findings."}},"type":"object","title":"AssetFindingsResponse","description":"The findings block embedded in an asset's detail response.\n\nA single-key object rather than a bare array because that is the\nshape this endpoint has always served: a caller reads\n``asset[\"findings\"][\"findings\"]``. The paginated\n:class:`~phoenix.api.rest.schemas.findings.FindingListResponse` is a\ndifferent thing -- it answers ``/v1/findings/`` and carries the page\ncounters this embed has no room for.\n\nAttributes:\n    findings (list[FindingResponse]): The embedded findings."},"AssetListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"assets":{"items":{"$ref":"#/components/schemas/AssetResponse"},"type":"array","title":"Assets"}},"type":"object","required":["assets"],"title":"AssetListResponse","description":"Paginated list of assets.\n\nMatches api-3's ``AssetList`` schema.\n\nAttributes:\n    assets (list[AssetResponse]): Asset objects."},"AssetResponse":{"properties":{"id":{"type":"integer","title":"Id"},"value":{"type":"string","title":"Value"},"type":{"type":"string","title":"Type"},"tier":{"anyOf":[{"$ref":"#/components/schemas/AssetTier"},{"type":"null"}]},"organization_id":{"type":"integer","title":"Organization Id"},"private":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Private"}},"type":"object","required":["id","value","type","organization_id"],"title":"AssetResponse","description":"Public asset representation (list item).\n\nMatches api-3's ``Asset`` schema.\n\nAttributes:\n    id (int): Unique asset identifier.\n    value (str): Asset value (FQDN, IP address, etc.).\n    type (str): Asset type.\n    tier (AssetTier | None): Asset tier (low, medium, high).\n    organization_id (int): Owning organization ID.\n    private (bool | None): Whether the asset is private."},"AssetServiceListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"services":{"items":{"$ref":"#/components/schemas/AssetServiceResponse"},"type":"array","title":"Services"}},"type":"object","required":["services"],"title":"AssetServiceListResponse","description":"The network services discovered on one asset.\n\nAttributes:\n    services (list[AssetServiceResponse]): The asset's services."},"AssetServiceResponse":{"properties":{"id":{"type":"integer","title":"Id"},"port":{"type":"integer","title":"Port"},"protocol":{"type":"string","title":"Protocol"},"scheme":{"type":"string","title":"Scheme"},"name":{"type":"string","title":"Name"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"last_change":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Change"},"pic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pic"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail"},"response_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Length"},"changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Changed"},"technologies":{"items":{"type":"string"},"type":"array","title":"Technologies"}},"type":"object","required":["id","port","protocol","scheme","name"],"title":"AssetServiceResponse","description":"One network service discovered on an asset, with its technologies.\n\nMapped from the ``asset_network_service_details`` view, which lifts the\norganization onto each row so the read is Cerbos-scoped, and carries\nthe detected technologies as a viewonly relationship.\n\nPaths are deliberately absent: a service can accumulate thousands, so\nthey stay behind their own endpoint rather than inflating this payload.\n\nAttributes:\n    id (int): The asset-service link id.\n    port (int): Service port.\n    protocol (str): Transport protocol.\n    scheme (str): Application scheme.\n    name (str): Service name as detected.\n    version (str | None): Detected version, if any.\n    description (str | None): Free-text description, if any.\n    url (str | None): Convenience address, e.g.\n        ``https://example.com:443``.\n    last_change (datetime.datetime | None): When the service last\n        changed.\n    pic (str | None): Screenshot URL, if captured.\n    thumbnail (str | None): Screenshot thumbnail URL, if captured.\n    response_length (int | None): Last response size in bytes.\n    changed (bool | None): Whether the last scan saw a change.\n    technologies (list[str]): Names of the technologies detected on\n        this service."},"AssetTier":{"type":"string","enum":["low","medium","high"],"title":"AssetTier","description":"Tier (importance level) of an asset.\n\nStored as the lowercase token (``low``/``medium``/``high``) — the\nsame value the public API uses, so no wire<->storage mapping is\nneeded."},"AuditLogEntryResponse":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Unique identifier for the audit log entry"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"When the action occurred"},"actor_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Kind","description":"Kind of actor (organization, api_key, system)"},"actor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Id","description":"Identifier of the actor"},"operation":{"type":"string","title":"Operation","description":"The operation performed"},"operation_category":{"type":"string","title":"Operation Category","description":"Category of operation"},"resource_type":{"type":"string","title":"Resource Type","description":"Type of resource affected"},"resource_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource Id","description":"Identifier of the affected resource"},"status":{"type":"string","title":"Status","description":"Outcome status (success/failure)"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Additional context and changes"},"actor_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Type","description":"Previous name for actor_kind.","deprecated":true,"readOnly":true},"actor_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Name","description":"Name/email of the actor","readOnly":true},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address","description":"IP address of the actor","readOnly":true},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent","description":"User agent of the client","readOnly":true}},"type":"object","required":["id","timestamp","actor_kind","actor_id","operation","operation_category","resource_type","resource_id","status","actor_type","actor_name","ip_address","user_agent"],"title":"AuditLogEntryResponse","description":"Single audit log entry in API responses.\n\nAttributes:\n    id (str | None): UUID of the entry, serialized as string.\n    timestamp (datetime.datetime): When the action occurred (UTC).\n    actor_kind (str | None): Kind of actor, also served as the\n        deprecated ``actor_type``. (organization, api_key,\n        system, admin).\n    actor_id (str | None): Identifier of the actor.\n    operation (str): Operation identifier\n        (e.g. ``asset.create``).\n    operation_category (str): Category for grouping operations.\n    resource_type (str): Type of affected resource.\n    resource_id (str | None): Identifier of the affected resource.\n    status (str): Outcome — ``success`` or ``failure``.\n    metadata (dict | None): Additional context and changes."},"AuditLogListResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Total number of matching audit logs"},"page":{"type":"integer","title":"Page","description":"Current page number"},"per_page":{"type":"integer","title":"Per Page","description":"Number of items per page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"audit_logs":{"items":{"$ref":"#/components/schemas/AuditLogEntryResponse"},"type":"array","title":"Audit Logs","description":"List of audit log entries"}},"type":"object","required":["total","page","per_page","audit_logs"],"title":"AuditLogListResponse","description":"Paginated list of audit log entries.\n\n``total`` / ``page`` / ``per_page`` are redeclared as **required**:\nthey shipped before :class:`PaginatedResponse` existed, so\ninheriting the mixin's ``int | None`` defaults would narrow an\nalready-live contract to nullable. ``links`` is inherited as-is\nfrom the mixin.\n\nAttributes:\n    total (int): Total count of matching entries across all pages.\n    page (int): Current page number.\n    per_page (int): Number of items per page.\n    audit_logs (list[AuditLogEntryResponse]): Entries for the\n        current page."},"BeaconCreatedResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"integer","title":"Status"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"created":{"type":"string","format":"date-time","title":"Created"},"allowed_cidrs":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Cidrs"},"dns_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dns Server"},"interface_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interface Name"},"version":{"type":"integer","title":"Version"},"beacon_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beacon Token"},"rathole_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rathole Server"},"rathole_public_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rathole Public Port"},"wireguard_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wireguard Config"},"rathole_control_port":{"type":"integer","title":"Rathole Control Port","description":"Rathole control port (always the default)","readOnly":true}},"type":"object","required":["id","name","status","last_seen","created","version","rathole_control_port"],"title":"BeaconCreatedResponse","description":"Response from a successful beacon creation.\n\nAttributes:\n    wireguard_config (str | None): The WireGuard server config —\n        returned exactly once, at creation time; never stored or\n        retrievable again afterward."},"BeaconDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"integer","title":"Status"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"created":{"type":"string","format":"date-time","title":"Created"},"allowed_cidrs":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Cidrs"},"dns_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dns Server"},"interface_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interface Name"},"version":{"type":"integer","title":"Version"},"beacon_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beacon Token"},"rathole_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rathole Server"},"rathole_public_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rathole Public Port"},"rathole_control_port":{"type":"integer","title":"Rathole Control Port","description":"Rathole control port (always the default)","readOnly":true}},"type":"object","required":["id","name","status","last_seen","created","version","rathole_control_port"],"title":"BeaconDetailResponse","description":"Full beacon representation for organization-scoped management.\n\nExtends the list item with the auth token and rathole connection\nparameters — never returned by the machine-facing :class:`BeaconResponse`\nnor by the list. ``beacon start`` re-reads them from here when its local\nstate is gone, so they must stay on get and patch.\n\nAttributes:\n    version (int): Beacon version (1=self-managed, 2=rathole-managed).\n    beacon_token (str | None): Beacon authentication token.\n    rathole_server (str | None): Rathole server hostname or IP.\n    rathole_public_port (int | None): Rathole-allocated UDP port.\n    rathole_control_port (int): Rathole control port; the same\n        constant for every beacon."},"BeaconResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"integer","title":"Status"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"created":{"type":"string","format":"date-time","title":"Created"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["id","name","status","last_seen","created"],"title":"BeaconResponse","description":"Machine-facing beacon representation, carrying the config download.\n\nServed to machine actors listing a pentest's beacons, the counterpart\nof the legacy API's ``Beacon`` model — same ``url`` field, same\none-hour signed lifetime, so an artiacker reads either identically.\n\n``url`` is populated by the route from\n:meth:`~phoenix.services.workflows.beacon.BeaconLookupWorkflow\n.sign_client_configs`, not by this schema: signing is a network call\nand must not happen during serialization.\n\nAttributes:\n    url (str | None): Signed download URL for the beacon's WireGuard\n        client config, or ``None`` when the beacon has none stored."},"BeaconStatus":{"type":"integer","enum":[1,2,3],"title":"BeaconStatus","description":"Health status of a beacon."},"Body_pentests-findings-attachments":{"properties":{"attachment":{"type":"string","contentMediaType":"application/octet-stream","title":"Attachment","description":"The file to upload."}},"type":"object","required":["attachment"],"title":"Body_pentests-findings-attachments"},"CeremonyStepKind":{"type":"string","enum":["redirect","form_post"],"title":"CeremonyStepKind","description":"How the browser reaches the provider.\n\nNot a caller's choice: it is decided by the template's binding. Phoenix mints\nevery SAML template with HTTP-Redirect, because the login app can only follow\na URL, so a form arrives only for a template set to POST some other way -- one\nminted before that, or edited in the Zitadel console. Handled all the same.\n\nAttributes:\n    REDIRECT: Navigate to a URL. OIDC, and SAML over HTTP-Redirect.\n    FORM_POST: POST a form. SAML over HTTP-POST, where the signed request\n        travels in a field and there is no URL to follow."},"CreateAssetRequest":{"properties":{"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"asset_value":{"type":"string","title":"Asset Value"},"asset_type":{"$ref":"#/components/schemas/ImplementedAssetType"},"beacon_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beacon Id"},"tier":{"anyOf":[{"$ref":"#/components/schemas/AssetTier"},{"type":"null"}]}},"type":"object","required":["asset_value","asset_type"],"title":"CreateAssetRequest","description":"Request body for asset creation.\n\nMatches api-3's ``AddAsset`` schema.\n\nAttributes:\n    organization_id (int | None): Target organization. Defaults\n        to the actor's organization when omitted.\n    asset_value (str): Asset value, automatically lowercased.\n    asset_type (ImplementedAssetType): Asset type. ``domain`` is\n        accepted as a previous name for ``fqdn``.\n    beacon_id (int | None): Associated beacon ID.\n    tier (AssetTier | None): Asset tier override."},"CreateBeaconRequest":{"properties":{"name":{"type":"string","title":"Name"},"allowed_cidrs":{"items":{"type":"string"},"type":"array","title":"Allowed Cidrs"},"dns_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dns Server"},"interface_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interface Name"}},"type":"object","required":["name","allowed_cidrs"],"title":"CreateBeaconRequest","description":"Request body for provisioning a new organization beacon.\n\nField-level validation (name charset, private-CIDR, DNS IP format)\nis not duplicated here — ``BeaconProvisioningWorkflow`` already\nvalidates all of these and raises a clear ``ValueError`` (mapped to\n400) on failure; re-validating in the schema would just be a second\ncopy of the same rules to keep in sync.\n\nAttributes:\n    name (str): Unique beacon name within the organization.\n    allowed_cidrs (list[str]): Private CIDRs reachable through the\n        tunnel.\n    dns_server (str | None): Optional DNS server pushed to clients.\n    interface_name (str | None): Optional host interface for NAT\n        masquerade."},"CreateDomainRequest":{"properties":{"value":{"type":"string","title":"Value"},"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"kind":{"anyOf":[{"$ref":"#/components/schemas/VerificationKind"},{"type":"null"}],"description":"Optional. Only `domain_dns` is accepted; self-attestation is a portal action."}},"type":"object","required":["value"],"title":"CreateDomainRequest","description":"Request body for ``POST /v1/domains/``.\n\nAttributes:\n    value (str): The domain to claim (e.g. ``example.com``). Cleaned to\n        its bare FQDN and lower-cased by the service.\n    organization_id (int | None): Owning organization; defaults to the\n        caller's own organization when omitted.\n    kind (VerificationKind | None): Accepted only so a request for\n        ``domain_checkbox`` can be refused with a pointer to the portal\n        -- self-attestation is not available through the API. Omit it,\n        or send ``domain_dns``."},"CreateMachinePentestRequest":{"properties":{"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id","description":"Organization that owns the pentest. Required for partners; organization callers may omit it."},"add_assets":{"type":"boolean","title":"Add Assets","description":"Add the organization's existing assets to scope, including verified domains as wildcard assets (every domain, when the organization's integrator partner vouches for it).","default":true},"launch_recon":{"type":"boolean","title":"Launch Recon","description":"Launch a recon for the pentest. Adds those domains as wildcard assets if they are not already there.","default":true},"end_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date","description":"End date in ISO 8601 format, timezone-aware and in the future. Defaults to the organization's subscription end, which it may not exceed."},"name":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Name","description":"Display name. Generated from the slug when omitted."}},"type":"object","title":"CreateMachinePentestRequest","description":"Body for creating a machine (continuous) pentest.\n\nField-for-field the legacy ``CreateMachineEvent``, including the two\ndefaults: a create with an empty body seeds the pentest's scope from\nthe organization's assets and launches a recon over it.\n\nAttributes:\n    organization_id (int | None): Owning organization. Required for\n        partners, who must already hold delegated access to it;\n        omitted by an organization caller, whose own organization is\n        used.\n    add_assets (bool): Seed scope with the organization's existing\n        assets — verified domains as wildcards (every domain, when its\n        integrator partner vouches for it), the rest as plain in-scope\n        entries.\n    launch_recon (bool): Launch a recon once the pentest exists. It\n        needs somewhere to look, so it adds those domains as wildcards\n        even when ``add_assets`` is false.\n    end_date (datetime.datetime | None): When the pentest should end.\n        Defaults to the organization's subscription end and may not\n        outlast it.\n    name (str | None): Display name; generated from the slug when\n        omitted."},"CreateOrganizationInviteRequest":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"},"role":{"$ref":"#/components/schemas/OrganizationRole"}},"type":"object","required":["email","role"],"title":"CreateOrganizationInviteRequest","description":"Body for ``POST /v1/organizations/{id}/invites``.\n\nAttributes:\n    email (EmailStr): Invitee email address.\n    role (OrganizationRole): Membership role granted on redemption;\n        must be an invitable role (``owner`` is rejected)."},"CreateOrganizationRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Organization name"},"assets_limit":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Assets Limit","description":"Requested asset cap for the new organization"}},"type":"object","required":["name"],"title":"CreateOrganizationRequest","description":"Request body for organization creation (integrator flow).\n\nMatches the legacy ``api`` app's ``CreateOrganization`` model\nexactly — no ``billing_email``/``payment_token``: those belong to\nthe unrelated self-service signup flow, not integrator-initiated\ncreation.\n\nAttributes:\n    name (str): Organization name.\n    assets_limit (int | None): Requested asset cap; falls back to\n        a default when omitted, capped by the integrator's own\n        partner subscription limit."},"CreatePartnerInviteRequest":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"},"partner_id":{"type":"integer","title":"Partner Id"},"role":{"$ref":"#/components/schemas/PartnerRole"}},"type":"object","required":["email","partner_id","role"],"title":"CreatePartnerInviteRequest","description":"Body for ``POST /v1/invites/partner``.\n\nAttributes:\n    email (EmailStr): Invitee email address.\n    partner_id (int): Target partner (Cerbos scopes a partner\n        manager to their own; admins may target any).\n    role (PartnerRole): Membership role granted on redemption; must\n        be an invitable role (``owner`` is rejected)."},"CreateReportRequest":{"properties":{"type":{"$ref":"#/components/schemas/ReportType","description":"Report type to generate."},"start_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date","description":"Report period start (ISO 8601)."},"end_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date","description":"Report period end (ISO 8601)."},"minimum_severity":{"anyOf":[{"$ref":"#/components/schemas/Severity"},{"type":"null"}],"description":"Minimum severity for technical reports."},"include_status":{"anyOf":[{"items":{"$ref":"#/components/schemas/ReportFindingStatus"},"type":"array"},{"type":"null"}],"title":"Include Status","description":"Finding statuses to include."},"tz":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tz","description":"IANA timezone (e.g. 'Europe/Lisbon')."}},"type":"object","required":["type"],"title":"CreateReportRequest","description":"Request body for creating a new report.\n\nAttributes:\n    type (ReportType): Report type to generate.\n    start_date (datetime.datetime | None): Report period start.\n    end_date (datetime.datetime | None): Report period end.\n    minimum_severity (FindingSeverity | None): Minimum finding\n        severity for technical reports.\n    include_status (list[ReportFindingStatus] | None): Finding\n        statuses to include in technical reports.\n    tz (str | None): IANA timezone for date interpretation."},"CreateTriagerInviteRequest":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email"}},"type":"object","required":["email"],"title":"CreateTriagerInviteRequest","description":"Body for ``POST /v1/invites/triager``.\n\nTriager invites carry no scope or role — the kind is platform-wide.\n\nAttributes:\n    email (EmailStr): Invitee email address."},"DefaultPentestDurationResponse":{"properties":{"default_pentest_duration_days":{"type":"integer","title":"Default Pentest Duration Days"},"editable":{"type":"boolean","title":"Editable"},"duration":{"type":"integer","title":"Duration","description":"Previous name for default_pentest_duration_days.","deprecated":true,"readOnly":true},"unit":{"type":"string","title":"Unit","description":"Unit of `duration`. Always `days`.","deprecated":true,"readOnly":true}},"type":"object","required":["default_pentest_duration_days","editable","duration","unit"],"title":"DefaultPentestDurationResponse","description":"Response for the default pentest duration setting.\n\nAttributes:\n    default_pentest_duration_days (int): Default number of days for\n        integrator pentests.\n    editable (bool): Whether the integrator can modify the pentest\n        duration for their organizations.\n\nAlso served: ``duration`` and ``unit``, the names this setting has\nalways been read under."},"DeleteDomainRequest":{"properties":{"delete_assets":{"type":"boolean","title":"Delete Assets","description":"Also remove the assets under this domain that no other active proof covers. Defaults to true, the legacy behaviour.","default":true}},"type":"object","title":"DeleteDomainRequest","description":"Optional body for ``DELETE /v1/domains/{domain_id}``.\n\nLegacy's delete took no body and always removed the assets under the\ndomain; omitting the body keeps that behaviour.\n\nAttributes:\n    delete_assets (bool): Also remove the organization's assets under\n        the domain that no other active proof still covers. Defaults to\n        ``True`` (legacy behaviour); send ``false`` to keep the assets\n        and remove only the domain."},"DocumentLanguage":{"type":"string","enum":["en","pt","de","fr","es"],"title":"DocumentLanguage","description":"Language a document is rendered in.\n\nMembers are ISO 639-1 codes. This docstring is served as the\nfield description in the public OpenAPI spec, so keep it about the\ncontract; the reasoning behind the closed set is on\n:attr:`~phoenix.db.models.document.Document.language` and in\nrevision ``a3f8d2c15e47``.\n\nThe values are what ``documents.language`` stores (a native\nPostgreSQL enum of member *values*, unlike ``document_type``\nabove -- NULL there for the CSV exports, which render no text)\nand what the report-generation microservice takes on the wire,\nwhere they must match its own ``Language`` enum label for label —\nit ships translations for exactly these five and answers anything\nelse with English, so a member added here without one there is\nsilently wrong rather than an error."},"DomainListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"domains":{"items":{"$ref":"#/components/schemas/DomainResponse"},"type":"array","title":"Domains"}},"type":"object","required":["domains"],"title":"DomainListResponse","description":"Paginated list response for both domain listings.\n\nAttributes:\n    domains (list[DomainResponse]): The authorized, paginated domains."},"DomainResponse":{"properties":{"id":{"type":"integer","title":"Id"},"value":{"type":"string","title":"Value"},"organization_id":{"type":"integer","title":"Organization Id"},"verified":{"type":"boolean","title":"Verified"},"kind":{"$ref":"#/components/schemas/VerificationKind"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"verification_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Token"},"verification_txt_record":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Txt Record"}},"type":"object","required":["id","value","organization_id","verified","kind","created_at"],"title":"DomainResponse","description":"A domain the organization claims, in the documented legacy shape.\n\nAttributes:\n    id (int): The domain's identifier (the verification's primary key;\n        legacy ids were preserved by the data migration).\n    value (str): The domain.\n    organization_id (int): The owning organization.\n    verified (bool): Whether the proof currently holds. Always ``True``\n        for a self-attested domain.\n    kind (VerificationKind): Which proof this entry carries (additive).\n    created_at (datetime.datetime): When the entry was created (additive).\n    verification_token (str | None): The bare challenge token -- *not*\n        the value to publish. Present only while a DNS proof is pending;\n        omitted once verified, as legacy did.\n    verification_txt_record (str | None): The exact TXT record value to\n        publish, ``ethiack-verification=<token>``, or the partner-branded\n        ``<partner>-ethiack-verification=<token>`` for an organization\n        under an integrator partner. Omitted once verified."},"EventListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"events":{"items":{"$ref":"#/components/schemas/PentestResponse"},"type":"array","title":"Events"}},"type":"object","required":["events"],"title":"EventListResponse","description":"The same list, published under the key the ``/v1`` surface serves.\n\nEach mount publishes one key, not both: carrying both would\nduplicate a list that reaches 500 items.\n\nAttributes:\n    pentests (list[PentestResponse]): Pentest objects, serialized\n        as ``events``."},"FindingAttachmentUploadResponse":{"properties":{"attachment_id":{"type":"integer","title":"Attachment Id","description":"Id of the uploaded attachment."}},"type":"object","required":["attachment_id"],"title":"FindingAttachmentUploadResponse","description":"Response from a successful finding-attachment upload.\n\nAttributes:\n    attachment_id (int): The stored attachment's id, to be listed in\n        a later submission's ``attachments`` so the ingest claims it."},"FindingDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"title":{"type":"string","title":"Title"},"md":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html"},"asset_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Asset Id"},"asset_value":{"type":"string","title":"Asset Value"},"status":{"$ref":"#/components/schemas/FindingStatus"},"previous_status":{"anyOf":[{"$ref":"#/components/schemas/FindingStatus"},{"type":"null"}]},"severity":{"type":"string","title":"Severity"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"requested_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Requested Score"},"cve":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cve"},"cvss_vector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cvss Vector"},"created":{"type":"string","format":"date-time","title":"Created"},"pentest_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"},"epss":{"anyOf":[{"$ref":"#/components/schemas/FindingEpssResponse"},{"type":"null"}],"description":"Latest EPSS sample for the finding's CVE, if any"},"kev":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Kev","description":"Whether the CVE is in the CISA KEV catalog"},"priority":{"type":"number","title":"Priority","description":"Triage urgency in [0, 3]; lower is more urgent","default":3.0},"event_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Slug","description":"Previous name for pentest_slug.","deprecated":true,"readOnly":true}},"type":"object","required":["id","title","asset_value","status","severity","created","event_slug"],"title":"FindingDetailResponse","description":"Single-finding view.\n\nWire-identical to :class:`FindingResponse`: ``md`` / ``html`` and\n``pentest_slug`` all moved to the base once list items had to carry\nthem too. What remains is ``pentest_id``, read by the route after\nvalidation and excluded from the response.\n\nAttributes:\n    pentest_id (int | None): The parent pentest's primary key. Never\n        serialized -- the wire names the pentest by ``pentest_slug``."},"FindingEpssResponse":{"properties":{"score":{"type":"number","title":"Score"},"percentile":{"type":"number","title":"Percentile"},"date":{"type":"string","format":"date","title":"Date"}},"type":"object","required":["score","percentile","date"],"title":"FindingEpssResponse","description":"The latest EPSS sample for a finding's CVE.\n\nAttributes:\n    score (float): EPSS score.\n    percentile (float): EPSS percentile.\n    date (datetime.date): Date the sample was published."},"FindingListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"findings":{"items":{"$ref":"#/components/schemas/FindingResponse"},"type":"array","title":"Findings"}},"type":"object","required":["findings"],"title":"FindingListResponse","description":"Paginated list of findings.\n\nAttributes:\n    findings (list[FindingResponse]): Finding objects."},"FindingMetadataResponse":{"properties":{"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","title":"FindingMetadataResponse","description":"Finding metadata (the ``data`` JSONB field).\n\nAttributes:\n    metadata (dict): Arbitrary JSON metadata."},"FindingResponse":{"properties":{"id":{"type":"integer","title":"Id"},"title":{"type":"string","title":"Title"},"md":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html"},"asset_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Asset Id"},"asset_value":{"type":"string","title":"Asset Value"},"status":{"$ref":"#/components/schemas/FindingStatus"},"previous_status":{"anyOf":[{"$ref":"#/components/schemas/FindingStatus"},{"type":"null"}]},"severity":{"type":"string","title":"Severity"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"requested_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Requested Score"},"cve":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cve"},"cvss_vector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cvss Vector"},"created":{"type":"string","format":"date-time","title":"Created"},"pentest_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"},"epss":{"anyOf":[{"$ref":"#/components/schemas/FindingEpssResponse"},{"type":"null"}],"description":"Latest EPSS sample for the finding's CVE, if any"},"kev":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Kev","description":"Whether the CVE is in the CISA KEV catalog"},"priority":{"type":"number","title":"Priority","description":"Triage urgency in [0, 3]; lower is more urgent","default":3.0},"event_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Slug","description":"Previous name for pentest_slug.","deprecated":true,"readOnly":true}},"type":"object","required":["id","title","asset_value","status","severity","created","event_slug"],"title":"FindingResponse","description":"Public finding representation (list item).\n\nMatches api-3's ``Finding`` schema.  Status integers are\nserialized as human-readable string labels.\n\nAttributes:\n    id (int): Unique finding identifier.\n    title (str): Finding title.\n    md (str | None): The finding write-up in Markdown.\n    html (str | None): The same write-up rendered as HTML. Both ride\n        list items as well as the single-finding reads, which is what\n        makes a page of findings heavy.\n    asset_id (int | None): Associated asset ID.\n    asset_value (str): Human-readable asset value.\n    status (FindingStatus): Lifecycle status (serialized as its\n        string label, e.g. ``\"open\"``).\n    previous_status (FindingStatus | None): Previous status.\n    severity (str): Severity label (e.g. ``\"high\"``).\n    score (float | None): CVSS score.\n    requested_score (float | None): Requested CVSS score.\n    cve (str | None): CVE identifier.\n    cvss_vector (str | None): CVSS vector string.\n    created (datetime.datetime): Creation timestamp.\n    pentest_slug (str | None): Slug of the pentest this finding belongs\n        to, so a list item can link to it. Filled by the route (the\n        pentest is a separate aggregate); ``None`` for a finding with\n        no pentest. Also served under its pre-rename name,\n        ``event_slug``.\n    epss (FindingEpssResponse | None): Latest EPSS sample for the\n        finding's CVE, or\n        ``None`` when it has no CVE or no sample. Filled by the route\n        from :meth:`FindingService.derived_map` — see ``kev``.\n    priority (float): Triage urgency in ``[0, 3]``, lower being more\n        urgent. Derived from the finding's severity/score and its\n        asset's tier, so it is filled by the route from\n        :meth:`FindingService.derived_map` — see ``kev``."},"FindingStatus":{"type":"string","enum":["draft","open","triaged","invalid","fixed","duplicate","accepted"],"title":"FindingStatus","description":"Finding lifecycle status.\n\n``DRAFT`` is the author-private pre-submission state: a hacker (or\nmachine) composes the finding as a draft, then ``submit`` advances\nit to ``OPEN`` / ``TRIAGED``, at which point it becomes visible to\nthe event's other members. Drafts are only ever seen by their\nauthor (and privileged actors)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"IdentityDomainRequest":{"properties":{"domain":{"type":"string","maxLength":225,"minLength":1,"title":"Domain","description":"Domain to prove control of, e.g. acme.com. Case and a trailing dot are normalised."}},"type":"object","required":["domain"],"title":"IdentityDomainRequest","description":"A domain the organization claims to control.\n\nAttributes:\n    domain (str): The domain to prove, in any spelling. Case, a trailing dot\n        and Unicode are canonicalised server-side, so a customer pasting\n        ``ACME.com.`` gets the same row as one typing ``acme.com``."},"IdentityDomainResponse":{"properties":{"id":{"type":"integer","title":"Id"},"domain":{"type":"string","title":"Domain"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At"},"last_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked At"},"record_name":{"type":"string","title":"Record Name"},"record_name_relative":{"type":"string","title":"Record Name Relative"},"record_value":{"type":"string","title":"Record Value"}},"type":"object","required":["id","domain","verified_at","last_checked_at","record_name","record_name_relative","record_value"],"title":"IdentityDomainResponse","description":"A registered domain and everything needed to publish its proof.\n\nThe two record fields are computed rather than stored. They exist because\n**the single most common failure in DNS verification is pasting the wrong one\nof them**: most provider UIs take the name *relative to the zone* and append\nthe domain themselves, so pasting the FQDN into that field yields\n``_ethiack-identity-challenge.acme.com.acme.com``, which resolves to nothing\nand reads as \"your check is broken\". Returning both, named for which is which,\nis cheaper than the support conversation.\n\nAttributes:\n    id (int): Row id, for the delete route. Re-checking is the background\n        sweep's job and has no endpoint.\n    domain (str): The canonicalised domain.\n    verified_at (datetime | None): When the proof was last observed, or\n        ``None`` while unproven. Cleared when a check no longer finds the\n        record, so this is current state rather than history.\n    last_checked_at (datetime | None): When the domain was last looked up,\n        whatever the outcome. A proof that is set but stale stops admitting:\n        both fields matter, not just the first.\n    record_name (str): Fully-qualified name to create the TXT record at.\n    record_name_relative (str): The same name as most DNS provider UIs want\n        it — relative to the zone, without the domain suffix.\n    record_value (str): The TXT value to publish: the token, bare."},"IdentityDomainsResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"domains":{"items":{"$ref":"#/components/schemas/IdentityDomainResponse"},"type":"array","title":"Domains"},"record_label":{"type":"string","title":"Record Label","default":"_ethiack-identity-challenge"}},"type":"object","required":["domains"],"title":"IdentityDomainsResponse","description":"A list wrapper, so the payload can gain fields without a breaking change.\n\nAttributes:\n    domains (list[IdentityDomainResponse]): The organization's domains.\n    record_label (str): The underscored label every record shares, surfaced so\n        a UI can explain the convention once rather than per row."},"IdpTemplateResponse":{"properties":{"zitadel_idp_id":{"type":"string","title":"Zitadel Idp Id","description":"The Zitadel IdP template this organization's federated logins now run against."},"protocol":{"type":"string","enum":["oidc","saml"],"title":"Protocol","description":"Which protocol family the template configures."},"acs_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Acs Url","description":"SAML only. The Assertion Consumer Service URL the customer must configure in their IdP. Unique to this template, which is what keeps one customer's assertions off another's connection."},"sp_metadata_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sp Metadata Url","description":"SAML only. Zitadel's **SP** metadata for this template, for customers who prefer importing it over manual entry. Named for the direction: the request bodies' `metadata_url` is the opposite one, the customer's IdP metadata that Zitadel fetches."},"certificate_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certificate Url","description":"SAML only. Zitadel's signing certificate for this template."},"slo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slo Url","description":"SAML only. Single-logout endpoint for this template."}},"type":"object","required":["zitadel_idp_id","protocol"],"title":"IdpTemplateResponse","description":"The template as it now stands, plus what the customer has to configure.\n\nCarries **no credential**: Zitadel never returns a client secret, and this\ndoes not echo the one that was sent."},"ImplementedAssetType":{"type":"string","enum":["fqdn","ip","cidr"],"title":"ImplementedAssetType","description":"Asset types that can be created through the public API.\n\nA subset of :class:`~phoenix.db.models.asset.AssetType`: the rest\nare produced by discovery rather than declared by a caller.\n\nA private ``CIDR`` additionally requires a ``beacon_id`` — see\n:meth:`CreateAssetRequest._private_ranges_need_a_beacon`."},"InviteKind":{"type":"string","enum":["organization","partner","triager"],"title":"InviteKind","description":"Which user kind an account invite grants on redemption.\n\nDoubles as the joined-table-inheritance discriminator: ``TRIAGER``\nis the base :class:`Invite` (no grant payload); ``ORGANIZATION`` /\n``PARTNER`` select the typed child.\n\nAttributes:\n    ORGANIZATION: Membership in the invite's organization.\n    PARTNER: Membership in the invite's partner.\n    TRIAGER: The triager kind (platform-scoped, no payload)."},"InviteStatus":{"type":"string","enum":["pending","redeemed","revoked"],"title":"InviteStatus","description":"The single terminal-state of an invite's lifecycle.\n\nA live invite is ``PENDING``; it moves once, irreversibly, to exactly\none terminal state. Modelling this as one enum (rather than separate\nper-outcome timestamps) makes \"redeemed AND revoked\" unrepresentable\nrather than merely constraint-guarded, and mirrors\n:class:`~phoenix.db.models.pentest_hacker_invite.PentestHackerInviteStatus`.\nExpiry is NOT a member — it is passive and time-derived from\n``expires_at``.\n\nAttributes:\n    PENDING: Live and awaiting redemption (paired with a null\n        ``resolved_at``).\n    REDEEMED: Consumed by its invitee.\n    REVOKED: Rescinded by a manager before redemption."},"LaunchListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"launches":{"items":{"$ref":"#/components/schemas/LaunchResponse"},"type":"array","title":"Launches"}},"type":"object","required":["launches"],"title":"LaunchListResponse","description":"List of launch records for a pentest.\n\nAttributes:\n    launches (list[LaunchResponse]): Launch objects."},"LaunchResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Launch ID."},"pentest_id":{"type":"integer","title":"Pentest Id","description":"Parent pentest ID."},"machine_user_id":{"type":"string","format":"uuid","title":"Machine User Id","description":"Owning machine user ID."},"mode":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mode","description":"Artiacker mode code."},"status":{"$ref":"#/components/schemas/LaunchStatus","description":"Execution status."},"error_info":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Info","description":"Error message when the launch failed."},"sub_job_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sub Job Id","description":"Linked scan_sub_jobs row."},"start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time","description":"Execution start time."},"finish_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finish Time","description":"Execution end time."},"created":{"type":"string","format":"date-time","title":"Created","description":"Creation timestamp."}},"type":"object","required":["id","pentest_id","machine_user_id","status","created"],"title":"LaunchResponse","description":"Single launch record in a response.\n\nAttributes:\n    id (int): Launch primary key.\n    pentest_id (int): Parent pentest id.\n    machine_user_id (UUID): Machine user that owns the launch.\n    mode (int | None): Artiacker mode code (see\n        :class:`~phoenix.db.models.artiacker.ArtiackerModeType`).\n    status (LaunchStatus): Execution status.\n    error_info (str | None): Truncated error message when the\n        launch ended in :attr:`LaunchStatus.ERROR`.\n    sub_job_id (int | None): Linked ``scan_sub_jobs`` row.\n    start_time (datetime.datetime | None): Execution start.\n    finish_time (datetime.datetime | None): Execution end.\n    created (datetime.datetime): Creation timestamp."},"LaunchStatus":{"type":"string","enum":["pending","in_progress","finished","error","canceled"],"title":"LaunchStatus","description":"Human-readable launch execution status.\n\nMaps to ``StatusEnum`` integer values stored in the database."},"MemberLinkOutcome":{"properties":{"email":{"type":"string","title":"Email","description":"The member's address the link is keyed on."},"status":{"type":"string","title":"Status","description":"What happened: failed, blocked, miskeyed, or no idp identity."},"detail":{"type":"string","title":"Detail","description":"Why, and what to change."}},"type":"object","required":["email","status","detail"],"title":"MemberLinkOutcome","description":"One member the link run could not resolve, for the owner to act on."},"MemberLinkSummaryResponse":{"properties":{"linked":{"type":"integer","title":"Linked","description":"Members newly linked by this run."},"already_linked":{"type":"integer","title":"Already Linked","description":"Members that were already linked."},"needs_attention":{"type":"integer","title":"Needs Attention","description":"Members that could not be linked as they are."},"unresolved":{"items":{"$ref":"#/components/schemas/MemberLinkOutcome"},"type":"array","title":"Unresolved","description":"The members counted in ``needs_attention``, with reasons."}},"type":"object","required":["linked","already_linked","needs_attention","unresolved"],"title":"MemberLinkSummaryResponse","description":"Result of linking an organization's members to its IdP.\n\nCounts plus only the members that need attention -- a fully successful run\nreturns empty ``needs_attention`` and no per-member rows."},"OidcTemplateRequest":{"properties":{"protocol":{"type":"string","const":"oidc","title":"Protocol","description":"Discriminator. OIDC is preferred where the customer supports it: the code exchange binds to this specific provider."},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Display name shown on the login screen."},"issuer":{"type":"string","minLength":1,"title":"Issuer","description":"The provider's OIDC issuer URL. Zitadel discovers the endpoints from its well-known document."},"client_id":{"type":"string","minLength":1,"title":"Client Id","description":"OAuth client id the customer registered for Zitadel."},"client_secret":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Client Secret","description":"The matching secret. Required when creating; on an update, omit it to keep the stored one — Zitadel preserves a secret that is absent from the request, so an issuer change needs no secret round-trip."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Scopes requested; must include openid."}},"type":"object","required":["protocol","name","issuer","client_id"],"title":"OidcTemplateRequest","description":"Create or update a generic-OIDC template.\n\nCovers every enterprise OIDC customer — Okta, Entra ID, Keycloak, Auth0 —\nbecause Zitadel does not distinguish them."},"OrganizationBeaconListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"beacons":{"items":{"$ref":"#/components/schemas/OrganizationBeaconSummaryResponse"},"type":"array","title":"Beacons"}},"type":"object","required":["beacons"],"title":"OrganizationBeaconListResponse","description":"Organization-scoped list of beacons.\n\nItems carry no credentials or configs; see\n:class:`OrganizationBeaconSummaryResponse`.\n\nAttributes:\n    beacons (list[OrganizationBeaconSummaryResponse]): Beacon objects."},"OrganizationBeaconSummaryResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"integer","title":"Status"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"created":{"type":"string","format":"date-time","title":"Created"},"allowed_cidrs":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Cidrs"},"dns_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dns Server"},"interface_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interface Name"}},"type":"object","required":["id","name","status","last_seen","created"],"title":"OrganizationBeaconSummaryResponse","description":"One item of the organization beacon list.\n\nIdentity, liveness and the network settings a table of beacons shows. No\ncredentials: the token and the rathole parameters are on the detail.\n\nAttributes:\n    allowed_cidrs (list[str] | None): Private CIDRs reachable through\n        the tunnel.\n    dns_server (str | None): DNS server pushed to clients.\n    interface_name (str | None): Host interface for NAT masquerade."},"OrganizationInviteListResponse":{"properties":{"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"invites":{"items":{"$ref":"#/components/schemas/OrganizationInviteResponse"},"type":"array","title":"Invites"}},"type":"object","required":["total","page","per_page","invites"],"title":"OrganizationInviteListResponse","description":"Paginated organization-invite list.\n\n``total`` / ``page`` / ``per_page`` are redeclared as **required**:\nthey shipped before :class:`PaginatedResponse` existed, so\ninheriting the mixin's ``int | None`` defaults would narrow an\nalready-live contract to nullable. ``links`` is inherited as-is\nfrom the mixin.\n\nAttributes:\n    total (int): Total invites matching the filters (same scope as\n        the page).\n    page (int): Current page number.\n    per_page (int): Page size.\n    invites (list[OrganizationInviteResponse]): The page of invites."},"OrganizationInviteResponse":{"properties":{"id":{"type":"integer","title":"Id"},"kind":{"$ref":"#/components/schemas/InviteKind"},"email":{"type":"string","title":"Email"},"status":{"$ref":"#/components/schemas/InviteStatus"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"},"redeemed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redeemed By User Id"},"expired":{"type":"boolean","title":"Expired"},"live":{"type":"boolean","title":"Live"},"organization_id":{"type":"integer","title":"Organization Id"},"role":{"$ref":"#/components/schemas/OrganizationRole"}},"type":"object","required":["id","kind","email","status","created_at","expires_at","resolved_at","redeemed_by_user_id","expired","live","organization_id","role"],"title":"OrganizationInviteResponse","description":"An organization invite as managers see it (never the token).\n\nAttributes:\n    organization_id (int): Target organization.\n    role (OrganizationRole): Membership role granted on redemption."},"OrganizationListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"organizations":{"items":{"$ref":"#/components/schemas/OrganizationResponse"},"type":"array","title":"Organizations"}},"type":"object","required":["organizations"],"title":"OrganizationListResponse","description":"Paginated list of organizations.\n\nAttributes:\n    organizations (list[OrganizationResponse]): Organization objects."},"OrganizationResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"assets_limit":{"type":"integer","title":"Assets Limit"}},"type":"object","required":["id","name","assets_limit"],"title":"OrganizationResponse","description":"Public organization representation (api-3 ``Organization`` parity).\n\nMatches api-3's minimal body: identity plus the computed,\nsubscription-derived asset cap. ``assets_limit`` is not a column —\nthe route supplies it via\n``OrganizationSubscriptionService.get_assets_limit``.\n\nAttributes:\n    id (int): Unique organization identifier.\n    name (str): Organization name.\n    assets_limit (int): Asset cap from the current (or pending)\n        subscription; ``0`` when none applies."},"OrganizationRole":{"type":"string","enum":["owner","admin","analyst","viewer"],"title":"OrganizationRole","description":"Sub-role within a :data:`ActorKind.ORGANIZATION` context.\n\nSnake_case wire values are used in the DB, in JWT claims synced\nfrom Zitadel, and as components of Cerbos role strings\n(e.g. ``\"org_admin\"``).\n\nAttributes:\n    OWNER: Full authority over the organization.\n    ADMIN: Administrative authority below the owner.\n    ANALYST: Read and write on engagements, findings, assets.\n    VIEWER: Read-only."},"PartnerAccessLevel":{"type":"string","enum":["none","read_only","full"],"title":"PartnerAccessLevel","description":"How much delegated access the org grants its managing partner.\n\nStored by member NAME (``NONE``/``READ_ONLY``/``FULL``) per the\nproject's enum-column convention; the lowercase ``value`` is the\nwire form that reaches Cerbos principal attributes.\n\nAttributes:\n    NONE: No delegated access (the default; replaces the legacy\n        ``accept_impersonation = false``).\n    READ_ONLY: The partner may operate the org read-only. Enforced\n        by the ``operating_partner`` / ``operating_partner_writer``\n        Cerbos composites (ENG-1467): ``operating_partner`` grants\n        reads at any access level, while ``operating_partner_writer``\n        additionally requires ``operating_access == \"full\"``, so a\n        ``READ_ONLY``-pinned partner never reaches a write.\n    FULL: The partner may operate the org, reads and writes\n        (models legacy impersonation's write surface)."},"PartnerAccessUpdateRequest":{"properties":{"level":{"$ref":"#/components/schemas/PartnerAccessLevel","description":"Delegated access granted to the managing partner."}},"type":"object","required":["level"],"title":"PartnerAccessUpdateRequest","description":"Body for updating an organization's partner access consent.\n\nAttributes:\n    level (PartnerAccessLevel): Delegated access granted to the\n        managing partner."},"PartnerInviteListResponse":{"properties":{"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"invites":{"items":{"$ref":"#/components/schemas/PartnerInviteResponse"},"type":"array","title":"Invites"}},"type":"object","required":["total","page","per_page","invites"],"title":"PartnerInviteListResponse","description":"Paginated partner-invite list.\n\n``total`` / ``page`` / ``per_page`` are redeclared as **required**\nfor the same reason as :class:`OrganizationInviteListResponse`.\n\nAttributes:\n    total (int): Total invites matching the filters.\n    page (int): Current page number.\n    per_page (int): Page size.\n    invites (list[PartnerInviteResponse]): The page of invites."},"PartnerInviteResponse":{"properties":{"id":{"type":"integer","title":"Id"},"kind":{"$ref":"#/components/schemas/InviteKind"},"email":{"type":"string","title":"Email"},"status":{"$ref":"#/components/schemas/InviteStatus"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"},"redeemed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redeemed By User Id"},"expired":{"type":"boolean","title":"Expired"},"live":{"type":"boolean","title":"Live"},"partner_id":{"type":"integer","title":"Partner Id"},"role":{"$ref":"#/components/schemas/PartnerRole"}},"type":"object","required":["id","kind","email","status","created_at","expires_at","resolved_at","redeemed_by_user_id","expired","live","partner_id","role"],"title":"PartnerInviteResponse","description":"A partner invite as managers see it (never the token).\n\nAttributes:\n    partner_id (int): Target partner.\n    role (PartnerRole): Membership role granted on redemption."},"PartnerRole":{"type":"string","enum":["owner","admin","billing_manager","technical_manager"],"title":"PartnerRole","description":"Sub-role within a :data:`ActorKind.PARTNER` context.\n\nSnake_case wire values are used in the DB, in JWT claims synced\nfrom Zitadel, and as components of Cerbos role strings\n(e.g. ``\"partner_owner\"``).\n\nAttributes:\n    OWNER: Full authority over the partner account.\n    ADMIN: Administrative authority below the owner.\n    BILLING_MANAGER: Limited to billing-related operations.\n    TECHNICAL_MANAGER: Technical operations on managed orgs\n        (reports, scope) without billing authority."},"PatchPentestAssetRequest":{"properties":{"in_scope":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In Scope"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"wildcard":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Wildcard"}},"type":"object","title":"PatchPentestAssetRequest","description":"Request body for updating a pentest asset.\n\nAll fields are optional — only provided fields are updated.\n\nAttributes:\n    in_scope (bool | None): New in-scope status.\n    description (str | None): New description.\n    wildcard (bool | None): New wildcard setting."},"PatchPentestScopeRequest":{"properties":{"in_scope":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"In Scope","description":"Asset IDs to mark as in-scope."},"out_of_scope":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Out Of Scope","description":"Asset IDs to mark as out-of-scope."}},"type":"object","title":"PatchPentestScopeRequest","description":"Request body for bulk scope updates.\n\nMoves assets between in-scope and out-of-scope.  An asset ID\nmust not appear in both sets.\n\nAttributes:\n    in_scope (set[int]): Asset IDs to mark as in-scope.\n    out_of_scope (set[int]): Asset IDs to mark as out-of-scope."},"PentestAssetListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"assets":{"items":{"$ref":"#/components/schemas/PentestAssetResponse"},"type":"array","title":"Assets"}},"type":"object","required":["assets"],"title":"PentestAssetListResponse","description":"Paginated list of pentest assets.\n\nShared by the paginated ``list_pentest_scope_assets`` read and, as a\nnested field, by ``PentestScopeResponse`` (the non-paginated\n``GET``/``PATCH /scope/`` combined in/out-of-scope view). Those two\nroutes exclude ``total``/``page``/``per_page``/``links`` on each\nnested list via ``response_model_exclude`` so they keep their\npre-pagination shape.\n\nAttributes:\n    assets (list[PentestAssetResponse]): Pentest-asset objects."},"PentestAssetResponse":{"properties":{"id":{"type":"integer","title":"Id"},"value":{"type":"string","title":"Value"},"type":{"type":"string","title":"Type"},"in_scope":{"type":"boolean","title":"In Scope"},"private":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Private"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"wildcard":{"type":"boolean","title":"Wildcard","default":false},"beacon_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beacon Id"},"attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Attachment Url"}},"type":"object","required":["id","value","type","in_scope"],"title":"PentestAssetResponse","description":"Flattened pentest-asset representation.\n\nCombines fields from both ``PentestAssets`` (link record) and\n``Asset`` (the actual asset).  The ``id`` field is the **asset**\nID, matching the api-3 contract.\n\nAttributes:\n    id (int): Asset ID.\n    value (str): Asset value (FQDN, IP, etc.). Wildcard domains\n        are prefixed with ``*.``.\n    type (str): Asset type (fqdn, ip, …).\n    in_scope (bool): Whether the asset is in scope.\n    private (bool | None): Whether the asset is private.\n    description (str | None): Scope description.\n    wildcard (bool): Whether wildcard matching is enabled.\n    beacon_id (int | None): Associated beacon ID.\n    attachment_url (str | None): Short-lived signed URL to download the\n        asset's uploaded app bundle (APK / IPA). Null when the asset is\n        out of scope or has no bundle, and also when storage reports the\n        bundle missing or its stored location cannot be signed. The scanner\n        skips an app whose URL is null. The scope routes fill it in from\n        :class:`~phoenix.services.workflows.scope_attachments.ScopeAttachmentWorkflow`."},"PentestDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"pic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pic"},"pentest_type":{"type":"string","title":"Pentest Type"},"pentest_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Category"},"status":{"type":"string","title":"Status"},"dimension_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dimension Type"},"organization_id":{"type":"integer","title":"Organization Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"created":{"type":"string","format":"date-time","title":"Created"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"md":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html"},"eh":{"type":"integer","title":"Eh","default":0},"draft":{"type":"boolean","title":"Draft","default":false},"configuration_finished":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Configuration Finished"},"open_for_applications":{"type":"boolean","title":"Open For Applications","default":false},"active_recon_enabled":{"type":"boolean","title":"Active Recon Enabled","default":false},"final_report_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Final Report Url"},"executive_report_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Executive Report Url"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data"},"event_type":{"type":"string","title":"Event Type","description":"Previous name for pentest_type.","deprecated":true,"readOnly":true},"event_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Category","description":"Previous name for pentest_category.","deprecated":true,"readOnly":true}},"type":"object","required":["id","slug","name","pentest_type","status","organization_id","start_date","end_date","created","event_type","event_category"],"title":"PentestDetailResponse","description":"Detailed test representation (single-item view).\n\nExtends :class:`PentestResponse` with configuration, location,\ncontent, and report fields.\n\nAttributes:\n    latitude (float | None): Pentest latitude.\n    longitude (float | None): Pentest longitude.\n    md (str | None): Pentest description in Markdown.\n    html (str | None): Pentest description in HTML.\n    eh (int): Pentest bounty balance.\n    draft (bool): Whether the test is in draft state.\n    configuration_finished (bool | None): Configuration complete flag.\n    open_for_applications (bool): Whether hackers can apply.\n    active_recon_enabled (bool): Active recon enabled.\n    final_report_url (str | None): Final report download URL.\n    executive_report_url (str | None): Executive report download URL.\n    data (dict): Arbitrary test metadata (JSONB)."},"PentestListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"pentests":{"items":{"$ref":"#/components/schemas/PentestResponse"},"type":"array","title":"Pentests"}},"type":"object","required":["pentests"],"title":"PentestListResponse","description":"Paginated list of pentests.\n\nShared by ``/v1/pentests`` and\n``/v1/organizations/{id}/pentests`` — both paginated list routes,\nwired through :class:`~phoenix.api.rest.pagination.Paginator`.\n\nAttributes:\n    pentests (list[PentestResponse]): Pentest objects."},"PentestResponse":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"pic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pic"},"pentest_type":{"type":"string","title":"Pentest Type"},"pentest_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Category"},"status":{"type":"string","title":"Status"},"dimension_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dimension Type"},"organization_id":{"type":"integer","title":"Organization Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"created":{"type":"string","format":"date-time","title":"Created"},"event_type":{"type":"string","title":"Event Type","description":"Previous name for pentest_type.","deprecated":true,"readOnly":true},"event_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Category","description":"Previous name for pentest_category.","deprecated":true,"readOnly":true}},"type":"object","required":["id","slug","name","pentest_type","status","organization_id","start_date","end_date","created","event_type","event_category"],"title":"PentestResponse","description":"Public test representation (list item).\n\nAttributes:\n    id (int): Pentest primary key.\n    slug (str): Unique pentest slug.\n    name (str): Pentest display name.\n    pic (str | None): Pentest picture URL, nullable.\n    pentest_type (int): Pentest type code (serialized as label).\n    pentest_category (int | None): Pentest category code, nullable\n        (serialized as label).\n    status (int): Lifecycle status (serialized as label).\n    dimension_type (int | None): Dimension type (serialized as label).\n    organization_id (int): Owning organization ID.\n    start_date (datetime.datetime): Scheduled start.\n    end_date (datetime.datetime): Scheduled end.\n    created (datetime.datetime): Creation timestamp."},"PentestScopeResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"in_scope":{"$ref":"#/components/schemas/PentestAssetListResponse"},"out_of_scope":{"$ref":"#/components/schemas/PentestAssetListResponse"},"beacons":{"anyOf":[{"items":{"$ref":"#/components/schemas/BeaconResponse"},"type":"array"},{"type":"null"}],"title":"Beacons"}},"type":"object","required":["in_scope","out_of_scope"],"title":"PentestScopeResponse","description":"Combined in-scope and out-of-scope asset lists for a pentest.\n\nAttributes:\n    in_scope (PentestAssetListResponse): Assets marked as in-scope.\n    out_of_scope (PentestAssetListResponse): Assets marked as\n        out-of-scope.\n    beacons (list[BeaconResponse] | None): Beacons linked to\n        in-scope assets.  Only populated for machine users."},"PentestScopeUrlListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"urls":{"items":{"type":"string"},"type":"array","title":"Urls"}},"type":"object","required":["urls"],"title":"PentestScopeUrlListResponse","description":"List of URLs derived from assets in the pentest's scope.\n\nAttributes:\n    urls (list[str]): URL strings (``scheme://host:port``)."},"PentestStatus":{"type":"integer","enum":[0,1,2,3,4,5,6],"title":"PentestStatus","description":"Pentest lifecycle status."},"PentestType":{"type":"integer","enum":[1,2],"title":"PentestType","description":"Pentest execution mode."},"ReconListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"recons":{"items":{"$ref":"#/components/schemas/ReconResponse"},"type":"array","title":"Recons"}},"type":"object","required":["recons"],"title":"ReconListResponse","description":"List of recon records for a pentest.\n\nAttributes:\n    recons (list[ReconResponse]): Recon objects."},"ReconResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Recon ID."},"status":{"$ref":"#/components/schemas/ReconStatus","description":"Execution status."},"created":{"type":"string","format":"date-time","title":"Created","description":"Creation timestamp."},"start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time","description":"Execution start time."},"finish_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finish Time","description":"Execution end time."},"pentest_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug","description":"Slug of the parent pentest."},"event_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Slug","description":"Previous name for pentest_slug.","deprecated":true,"readOnly":true}},"type":"object","required":["id","status","created","event_slug"],"title":"ReconResponse","description":"Single recon record in a response.\n\nAttributes:\n    id (int): Recon (launch) primary key.\n    status (ReconStatus): Execution status.\n    created (datetime.datetime): Creation timestamp.\n    start_time (datetime.datetime | None): Execution start.\n    finish_time (datetime.datetime | None): Execution end.\n    pentest_slug (str | None): Slug of the parent pentest. Also served\n        as the deprecated ``event_slug``."},"ReconStatus":{"type":"string","enum":["pending","in_progress","finished","error","canceled"],"title":"ReconStatus","description":"Human-readable recon execution status.\n\nMaps to ``StatusEnum`` integer values stored in the database."},"ReportDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"pentest_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"},"options":{"additionalProperties":true,"type":"object","title":"Options","description":"Generation options this report was requested with. Empty for types that take none."},"document_type":{"type":"string","title":"Document Type"},"status":{"type":"integer","title":"Status"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"start_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"},"generation_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Generation Date"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"convert_to_pdf":{"type":"boolean","title":"Convert To Pdf","default":true},"language":{"anyOf":[{"$ref":"#/components/schemas/DocumentLanguage"},{"type":"null"}]},"created":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created"},"event_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Slug","description":"Previous name for pentest_slug.","deprecated":true,"readOnly":true}},"type":"object","required":["id","document_type","status","event_slug"],"title":"ReportDetailResponse","description":"Detailed report representation (single-item view)."},"ReportFindingStatus":{"type":"string","enum":["fixed","triaged","informative"],"title":"ReportFindingStatus","description":"Finding statuses that can be included in a technical report.\n\n``informative`` selects best-practice findings -- those carrying the\nlowest severity -- rather than naming a status of its own."},"ReportListResponse":{"properties":{"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page"},"per_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"reports":{"items":{"$ref":"#/components/schemas/ReportResponse"},"type":"array","title":"Reports"}},"type":"object","required":["reports"],"title":"ReportListResponse","description":"Paginated list wrapper for :class:`ReportResponse`.\n\nShared by ``/v1/reports``, ``/v1/organizations/{id}/reports``, and\n``/v1/pentests/{slug}/reports`` — all three are paginated list\nroutes, so every returner is wired through\n:class:`~phoenix.api.rest.pagination.Paginator`.\n\nAttributes:\n    reports (list[ReportResponse]): Report objects."},"ReportPendingResponse":{"properties":{"id":{"type":"integer","title":"Id"},"code":{"type":"integer","title":"Code","default":202},"description":{"type":"string","title":"Description","default":"Report not yet available."},"success":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Success"}},"type":"object","required":["id"],"title":"ReportPendingResponse","description":"Answer for a report whose artifact has not been generated yet.\n\nA download answers ``302`` once the artifact exists. Until then the\nrequest is neither an error nor a redirect, so it answers ``202`` with\nthis body and the caller polls. Reporting a pending report as ``404``\ninstead would tell a poller to give up on a report still being built.\n\nAttributes:\n    code (int): HTTP status code; always ``202``.\n    description (str): Human-readable status.\n    id (int): The report's identifier, echoed so a poller can key on it.\n    success (bool | None): ``None`` — the outcome is not yet decided."},"ReportResponse":{"properties":{"id":{"type":"integer","title":"Id"},"pentest_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pentest Slug"},"options":{"additionalProperties":true,"type":"object","title":"Options","description":"Generation options this report was requested with. Empty for types that take none."},"document_type":{"type":"string","title":"Document Type"},"status":{"type":"integer","title":"Status"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"start_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"},"generation_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Generation Date"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"convert_to_pdf":{"type":"boolean","title":"Convert To Pdf","default":true},"language":{"anyOf":[{"$ref":"#/components/schemas/DocumentLanguage"},{"type":"null"}]},"created":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created"},"event_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Slug","description":"Previous name for pentest_slug.","deprecated":true,"readOnly":true}},"type":"object","required":["id","document_type","status","event_slug"],"title":"ReportResponse","description":"Public report representation.\n\nAttributes:\n    id (int): Document ID.\n    pentest_slug (str | None): Slug of the parent pentest. ``None`` for\n        org-wide reports (e.g. ``asset_inventory``).\n    document_type (int): Member of\n        :class:`~phoenix.common.DocumentType`.  Serialized as a\n        lowercase label (e.g. ``\"final_report\"``).\n    status (int): Lifecycle status — member of\n        :class:`~phoenix.common.StatusEnum`.\n    url (str | None): Generated document URL — set once the\n        post-commit handler finishes.\n    start_date (datetime | None): Report period start.\n    end_date (datetime | None): Report period end.\n    generation_date (datetime | None): When the document was\n        generated.\n    error (str | None): Generation error message, if any.\n    convert_to_pdf (bool): Whether the workflow converted the\n        DOCX output to PDF.  Always ``True`` for CSV-output\n        types.\n    language (DocumentLanguage | None): Language the document is\n        rendered in, serialized as its ISO 639-1 code (e.g.\n        ``\"en\"``).  ``None`` for the CSV exports\n        (``findings_csv``, ``asset_inventory``), which render no\n        text — not \"unknown\", and not a default worth substituting.\n    created (datetime | None): Creation timestamp."},"ReportType":{"type":"string","enum":["technical","executive","csv","asset_inventory","customer_facing","owasp_top_10","owasp_wstg","pci_dss"],"title":"ReportType","description":"Human-readable report type.\n\nMaps to :class:`~phoenix.common.DocumentType` integers."},"RetestInfoResponse":{"properties":{"created":{"type":"string","format":"date-time","title":"Created"},"started":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started"},"finished":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished"},"status":{"type":"string","title":"Status"},"uuid":{"type":"string","title":"Uuid"}},"type":"object","required":["created","status","uuid"],"title":"RetestInfoResponse","description":"Retest job information for a finding.\n\nAttributes:\n    created (datetime.datetime): When the retest job was created.\n    started (datetime.datetime | None): When the retest started.\n    finished (datetime.datetime | None): When the retest finished.\n    status (str): Job status label.\n    uuid (str): Unique identifier of the retest job."},"SamlTemplateRequest":{"properties":{"protocol":{"type":"string","const":"saml","title":"Protocol","description":"Discriminator."},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Display name shown on the login screen."},"metadata_url":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Metadata Url","description":"Public HTTPS URL of the IdP's SAML metadata. Fetched once, when this request is processed, and stored as the document; it is not re-fetched, so a signing-certificate rotation needs the configuration saved again. Exactly one of this or metadata_xml is required."},"metadata_xml":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Metadata Xml","description":"Base64-encoded SAML metadata document, for an IdP that publishes no reachable metadata endpoint. Like metadata_url, it goes stale on certificate rotation. Exactly one of this or metadata_url is required."},"sign_requests":{"type":"boolean","title":"Sign Requests","description":"Sign the AuthnRequest. Leaving this off lets anyone who can reach the IdP's SSO endpoint mint a request naming Zitadel.","default":true},"allow_sha1":{"type":"boolean","title":"Allow Sha1","description":"Sign with RSA-SHA1 instead of RSA-SHA256. SHA-1 is collision-broken; set this only for a legacy IdP that accepts nothing else, and record why.","default":false}},"type":"object","required":["protocol","name"],"title":"SamlTemplateRequest","description":"Create or update a SAML template.\n\nRequired for parity with legacy, whose SSO was SAML-only. Note the customer\nmust configure their IdP against the **per-template** ACS and metadata URLs\nreturned by this call, not against a shared endpoint."},"Severity":{"type":"integer","enum":[0,1,2,3,4,5],"title":"Severity","description":"Finding severity level."},"SsoConnectionResponse":{"properties":{"login_slug":{"type":"string","title":"Login Slug","description":"Org-scoped entry token in the login URL."},"zitadel_idp_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zitadel Idp Id","description":"The Zitadel IdP template, or null if unpointed."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether federated login is served."},"require_sso":{"type":"boolean","title":"Require Sso","description":"Whether members must authenticate through the IdP."},"provision_on_first_login":{"type":"boolean","title":"Provision On First Login","description":"Whether a first federated login may mint an account. Armed only through the provisioning workflow, which requires a DNS-proven domain; not settable here."},"provisioned_role":{"$ref":"#/components/schemas/OrganizationRole","description":"Role a provisioned account receives."},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When a configuration test last passed against the template now in service, or null if none has. `require_sso` cannot be set true while this is null: mandating SSO on a connection nobody has proved works locks the organization out, since its members hold no password. Re-pointing the template clears it, so a verification never outlives the thing it verified."},"protocol":{"anyOf":[{"type":"string","enum":["saml","unknown"]},{"type":"null"}],"title":"Protocol","description":"Which protocol family the template configures, as far as Phoenix can tell. Null when the connection points at no template. `saml` when `expected_issuer` is stored -- the mint workflow refuses to record a SAML template without one, so its presence is a positive signal, and it is the same discriminator the login path keys on (`sso_jit_login._issuer_verdict`). `unknown` for a connection that is pointed but stores no issuer: that is an OIDC template, or a SAML one pointed at by `PUT .../sso/template` alone, which records no issuer -- and nothing on the row separates the two. Answering `oidc` there would be a guess, and the guess would hide the ACS and metadata URLs a SAML customer cannot finish onboarding without."},"acs_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Acs Url","description":"SAML only. The Assertion Consumer Service URL the customer must configure in their IdP. Unique to this template, which is what keeps one customer's assertions off another's connection."},"sp_metadata_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sp Metadata Url","description":"SAML only. Zitadel's **SP** metadata for this template, for customers who prefer importing it over manual entry. Named for the direction: the request bodies' `metadata_url` is the opposite one, the customer's IdP metadata that Zitadel fetches."},"certificate_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certificate Url","description":"SAML only. Zitadel's signing certificate for this template."},"slo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slo Url","description":"SAML only. Single-logout endpoint for this template."}},"type":"object","required":["login_slug","enabled","require_sso","provision_on_first_login","provisioned_role"],"title":"SsoConnectionResponse","description":"An organization's connection, as an admin or its owner sees it.\n\nWider than ``GET /auth/sso/{login_slug}``, which is unauthenticated and\ndeliberately omits everything about what happens to *accounts*.\n\n**Carries the customer-facing SAML endpoints, which the write responses also\ncarry.** They used to come back only from ``POST|PUT .../sso/idp``, both\nadmin-only, so the organization owner -- who can already read this resource --\nhad no way to see the ACS and metadata URLs they have to paste into their own\nIdP. Every one of them is derived from ``zitadel_idp_id`` by\n``TemplateEndpoints`` and is published, non-secret metadata that Zitadel\nitself serves unauthenticated, so returning them here grants no authority the\nreader did not already hold. The descriptions are copied verbatim from\n``IdpTemplateResponse`` so the two surfaces cannot drift into describing\nthe same URL differently.\n\nNothing about the *provider* is echoed: no metadata document, no OIDC client\nid and no secret. Those are inputs, admin-only, and this is a read every\nowner holds."},"SsoPolicyRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether federated login is served for this organization. Refused while no template is set."},"require_sso":{"type":"boolean","title":"Require Sso","description":"Whether members must authenticate through the IdP. Enforced on every organization-scoped session: turning it on refuses every member session that did not come through this IdP, on its next request. Link existing members first. Turning it on for a connection that is not served is refused outright rather than merely discouraged, since it would leave nothing to log in with."}},"type":"object","required":["enabled","require_sso"],"title":"SsoPolicyRequest","description":"Organization-owner body: the policy applied on top of the template."},"SsoProvisioningRequest":{"properties":{"provision_on_first_login":{"type":"boolean","title":"Provision On First Login","description":"Whether a first federated login may mint an account. Arming is refused with no domain proven and re-confirmed within the proof window; disarming is always allowed, so an organization whose proof lapsed can still turn off what the lapsed proof no longer justifies."},"provisioned_role":{"anyOf":[{"$ref":"#/components/schemas/OrganizationRole"},{"type":"null"}],"description":"Role a provisioned account receives. Null keeps the role already set. Capped to viewer / analyst, so a misconfigured IdP cannot use provisioning to grant authority over existing members. Ignored when disarming."}},"type":"object","required":["provision_on_first_login"],"title":"SsoProvisioningRequest","description":"Organization-owner body: whether a first federated login may mint accounts.\n\nIts own sub-resource rather than two more fields on\n:class:`SsoPolicyRequest`, because arming is the one write on this connection\nwith a precondition the request cannot carry: at least one domain proven by\nDNS *and* re-confirmed inside the proof window. Folding it into the policy\nbody would mean either refusing an ordinary policy edit whenever no proof is\ncurrent, or letting a policy edit arm provisioning without one.\n\nPartial by design, unlike the two total bodies above. ``provisioned_role``\nomitted keeps the role already on the connection, so re-arming after a proof\nlapsed and was re-published does not silently reset it to a default the\ncustomer never chose."},"SsoTemplateRequest":{"properties":{"login_slug":{"type":"string","maxLength":63,"minLength":1,"pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$","title":"Login Slug","description":"Org-scoped entry token in the login URL: a lower-case DNS label, globally unique. It ends up in the customer's IdP configuration, so changing it invalidates entry links already handed out."},"zitadel_idp_id":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Zitadel Idp Id","description":"The Zitadel IdP template this organization's federated logins run against. Required rather than optional so omitting it cannot quietly unpoint a working connection; send an explicit null to unpoint one, which is refused while it is still enabled."}},"type":"object","required":["login_slug","zitadel_idp_id"],"title":"SsoTemplateRequest","description":"Admin onboarding body: which Zitadel template this org logs in through."},"SsoTestOutcome":{"type":"string","enum":["passed","ceremony_expired","no_template_on_intent","wrong_template","no_expected_issuer","no_assertion","unreadable_assertion","mismatched","no_asserted_email"],"title":"SsoTestOutcome","description":"What a completed configuration test found.\n\nAttributes:\n    PASSED: The ceremony would have been accepted by the login path.\n    NO_TEMPLATE_ON_INTENT: The redeemed intent names no IdP template. A\n        broken ceremony rather than a configuration fault.\n    WRONG_TEMPLATE: The intent names a template that is not this\n        connection's. Refused rather than reported against this connection:\n        an intent naming a template is not evidence it belongs to whoever\n        redeems it.\n    NO_EXPECTED_ISSUER: SAML answered, but no EntityID is stored to check\n        it against, so nothing verified which provider signed. An\n        administrator re-sending the customer's metadata records one.\n    NO_ASSERTION: The connection expects an EntityID but the ceremony\n        carried no assertion.\n    UNREADABLE_ASSERTION: An assertion arrived whose ``Issuer`` could not be\n        read.\n    MISMATCHED: The assertion was signed by a different EntityID than the\n        one stored -- usually the customer re-created their IdP. Repaired by\n        an administrator re-sending their metadata, which re-derives the\n        stored EntityID.\n    NO_ASSERTED_EMAIL: Every tenancy check passed but no address could be\n        read: none in Zitadel's claims (always the case for SAML) and none\n        in the assertion -- no ``emailAddress`` NameID and no single email\n        attribute (see :mod:`._saml_email`). Invisible without a test: a\n        login refuses such an identity with nothing to tell the customer.\n    CEREMONY_EXPIRED: The ceremony was never completed, or was completed too\n        long ago. **The one outcome that says nothing about the\n        configuration** -- the customer left the wizard open, which is the\n        most predictable thing to happen in a wizard. Tell them to start\n        again, not to look at their IdP."},"SsoTestResultRequest":{"properties":{"intent_id":{"type":"string","maxLength":255,"minLength":1,"title":"Intent Id","description":"The `id` query parameter from the return URL."},"intent_token":{"type":"string","maxLength":2048,"minLength":1,"title":"Intent Token","description":"The `token` query parameter from the return URL. Never logged or stored server-side."}},"type":"object","required":["intent_id","intent_token"],"title":"SsoTestResultRequest","description":"The intent the ceremony's return URL handed back.\n\nBoth values arrive in the browser's query string, so they are already\nexposed to history and access logs by Zitadel's own design. Sent in a body\nrather than as query parameters here so this service does not add a second\ncopy of the token to its own request logs.\n\nThe bounds match the federated login's (``FederatedAccountRequest``): the\ntoken's length is Zitadel's to choose, and v4.19 grew it from 46 characters\nto a 257-character signed token, which a 200 cap refused with a 422."},"SsoTestStartResponse":{"properties":{"step":{"$ref":"#/components/schemas/CeremonyStepKind","description":"Which shape this is. Decided by the template's binding, not by anything the caller asked for, so both must be handled."},"auth_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Url","description":"Navigate the browser here. Set only when step is `redirect`."},"form_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form Url","description":"POST the form here. Set only when step is `form_post`."},"form_fields":{"additionalProperties":{"type":"string"},"type":"object","title":"Form Fields","description":"Fields to POST, empty for a redirect. Submit every one as-is and as `type=\"text\"`; they carry the signed `SAMLRequest`."}},"type":"object","required":["step"],"title":"SsoTestStartResponse","description":"What the front-end must do next to run the ceremony.\n\nNot a bare URL. SAML's HTTP-POST binding returns a signed request to submit\nrather than an address to follow, so a caller that reads only ``auth_url``\nworks for OIDC and for HTTP-Redirect SAML and silently does nothing for a\ntemplate set to POST. Phoenix mints HTTP-Redirect, but a template minted\nbefore that, or edited in the Zitadel console, can still answer with a form."},"SsoTestVerdictResponse":{"properties":{"passed":{"type":"boolean","title":"Passed","description":"Whether a member's login would have been accepted. The one field a wizard should gate on; `outcome` says why when it is false."},"outcome":{"$ref":"#/components/schemas/SsoTestOutcome","description":"What was found. `passed` is the only accepting value. `ceremony_expired` is the only one that says nothing about the configuration -- it means the wizard was left open, so offer to start again rather than reporting a fault."},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id","description":"The Zitadel IdP template the ceremony ran against. Null on the three withholding outcomes above -- including `wrong_template`, where the intent did name one and it is deliberately not returned."},"expected_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Issuer","description":"The SAML EntityID stored for this connection. Null for an OIDC connection, and null for a SAML one that stores none -- reported as `no_expected_issuer` only when the ceremony carried an assertion to compare it against."},"asserted_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asserted Issuer","description":"The EntityID that actually signed the assertion, when one could be read. Compared against `expected_issuer`; when they differ, re-send the customer's metadata through the admin IdP update so the stored EntityID tracks it again."},"asserted_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asserted Email","description":"The address the provider released. Null on the three withholding outcomes above; on any other outcome it means none could be read -- for SAML, neither an `emailAddress` NameID nor an email attribute (`email`, `emailaddress`, `mail`, or the Entra claim URI). Logins refuse such an identity, and the customer has to release the address in one of those."},"tester_link":{"anyOf":[{"$ref":"#/components/schemas/TesterLink"},{"type":"null"}],"description":"What a passing test did about the tester's own account: `linked`, `already_linked`, `other_address` (the provider asserted someone else's address, so nothing was linked), `held_by_another`, or `not_linked`. Null unless the test passed. Linking is not enabling: a linked member signs in through the provider only once the connection is enabled."}},"type":"object","required":["passed","outcome"],"title":"SsoTestVerdictResponse","description":"What the ceremony found, in the terms the customer has to act on.\n\nSpecific where a federated *login* is deliberately opaque, and the\ndifference is the caller: a login answers an unauthenticated stranger, this\nanswers the organization's own owner about their own connection. Naming the\nfault is the entire point -- \"SSO did not work\" is what they already knew.\n\n**Three outcomes report no detail at all.** On `wrong_template`,\n`no_template_on_intent` and `ceremony_expired`, every value below that is\nread off the ceremony is withheld and comes back null -- the ceremony was\nnot this connection's, so its contents are not the caller's to read. Only\n`expected_issuer`, taken from the caller's own connection, is carried. Read\na null on those outcomes as \"withheld\", never as \"the provider sent\nnothing\"."},"SubmitFindingRequest":{"properties":{"asset":{"type":"string","title":"Asset","description":"Asset identifier/value."},"severity":{"type":"string","title":"Severity","description":"Severity label."},"cwe":{"type":"integer","title":"Cwe","description":"CWE identifier."},"cve":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cve","description":"CVE identifier."},"score":{"anyOf":[{"type":"number","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Score","description":"CVSS score (0-10.0)."},"cvss_vector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cvss Vector","description":"CVSS vector string."},"title":{"type":"string","title":"Title","description":"Finding title."},"markdown":{"type":"string","title":"Markdown","description":"Finding description in Markdown."},"attachments":{"items":{"type":"integer"},"type":"array","title":"Attachments","description":"Attachment IDs."},"beacon_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beacon Id","description":"Beacon the engine scanned through; the asset is resolved behind it."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Affected URL; resolves the finding to an asset."},"module":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Module","description":"Engine module that produced the finding."},"submodule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Submodule","description":"Engine submodule/template that produced the finding."},"confidence_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confidence Level","description":"Engine confidence label for the finding."},"scan_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scan Id","description":"Scan that produced the finding."},"job_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Job Id","description":"Job that produced the finding."},"sponsored_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sponsored Content","description":"Vendor content appended to the write-up."},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Engine metadata, surfaced in the triager view."}},"type":"object","required":["asset","severity","cwe","title","markdown"],"title":"SubmitFindingRequest","description":"Request body for submitting a finding against a pentest.\n\nAttributes:\n    asset (str): Asset identifier/value the finding targets.\n    severity (str): Finding severity label.\n    cwe (int): CWE identifier.\n    cve (str | None): CVE identifier (e.g. ``CVE-2024-1234``).\n    score (float | None): CVSS score (0-10.0).\n    cvss_vector (str | None): CVSS vector string.\n    title (str): Finding title.\n    markdown (str): Finding description in Markdown.\n    attachments (list[int]): Attachment IDs.\n    beacon_id (int | None): The beacon an engine scan ran through.\n        Engine submissions resolve their asset behind it.\n    url (str | None): The affected URL. Required for engine\n        submissions -- it is what resolves the finding to an asset.\n    module (str | None): The engine module that produced it.\n    submodule (str | None): The module's template/submodule.\n    confidence_level (str | None): The engine's confidence label.\n    scan_id (int | None): The scan that produced it, linked after\n        creation.\n    job_id (int | None): The job that produced it.\n    sponsored_content (str | None): Vendor content appended to the\n        write-up.\n    data (dict[str, Any]): Engine metadata, surfaced to triagers."},"SubmitFindingResponse":{"properties":{"result":{"type":"string","title":"Result","default":"success"},"finding_id":{"type":"string","title":"Finding Id","description":"Finding ID as hex string."}},"type":"object","required":["finding_id"],"title":"SubmitFindingResponse","description":"Response from a successful finding submission.\n\nAttributes:\n    result (str): Operation result (``\"success\"``).\n    finding_id (str): Created finding ID as hex string."},"SuccessResponse":{"properties":{"code":{"type":"integer","title":"Code"},"description":{"type":"string","title":"Description"},"id":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Id"},"success":{"type":"boolean","title":"Success","default":true}},"type":"object","required":["code","description"],"title":"SuccessResponse","description":"Standard response for successful write operations.\n\nMatches the ``SuccessResponse`` contract from api-3.\n\nAttributes:\n    code (int): HTTP status code.\n    description (str): Human-readable result description.\n    id (int | str | None): Created/affected resource ID.\n    success (bool): Always ``True`` for successful responses."},"TechnologyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"vendor":{"type":"string","title":"Vendor","default":""},"version":{"type":"string","title":"Version","default":""},"os":{"type":"string","title":"Os","default":""}},"type":"object","required":["id","name"],"title":"TechnologyResponse","description":"Public technology representation (list item).\n\nAttributes:\n    id (int): Technology primary key.\n    name (str): Technology name (e.g. ``\"wordpress\"``).\n    vendor (str): Vendor name.\n    version (str): Version string.\n    os (str): Operating system."},"TestBeaconRequest":{"properties":{"target":{"type":"string","title":"Target"}},"type":"object","required":["target"],"title":"TestBeaconRequest","description":"Request body for testing reachability through a beacon.\n\nAttributes:\n    target (str): Target to test reachability for, as ``host:port``."},"TestBeaconResponse":{"properties":{"reachable":{"type":"boolean","title":"Reachable"}},"type":"object","required":["reachable"],"title":"TestBeaconResponse","description":"Response body for a beacon reachability test.\n\nAttributes:\n    reachable (bool): Whether the target is reachable."},"TesterLink":{"type":"string","enum":["linked","already_linked","other_address","held_by_another","not_linked"],"title":"TesterLink","description":"What a passing test did about the tester's own account.\n\nA passing ceremony is the strongest evidence this system ever gets that the\nperson in front of it controls an identity at the organization's provider:\nthey are signed in to Ethiack, and they have just completed a real sign-in at\nthe provider in the same browser. So the tester's own account is linked to\nthat identity -- keyed on the identifier the provider actually sent, not on\nan address, which also makes it work for a template asserting opaque\npersistent ids.\n\nOnly ever the tester, and only when the provider asserted the tester's own\naddress. Anything else stays with ``POST .../sso/members/link``.\n\nAttributes:\n    LINKED: This call linked the tester's account to the identity.\n    ALREADY_LINKED: The identity already resolves to the tester's account.\n    OTHER_ADDRESS: The provider asserted an address that is not the\n        tester's -- typically an IT administrator testing with their own\n        account. Linking it would let that person sign in as the tester, so\n        nothing is linked.\n    HELD_BY_ANOTHER: The identity already belongs to a different account.\n    NOT_LINKED: Anything else: the tester has no Zitadel identity, is\n        already linked to this provider under another identifier, may not\n        link members (or that could not be checked), or Zitadel refused or\n        was unreachable. The verdict stands either way; linking is a\n        convenience on top of it."},"TriagerInviteListResponse":{"properties":{"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"},"_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Links","description":"RFC 8288 rel to URL. Always first; last unless the route opted out of counting; prev and next when applicable."},"invites":{"items":{"$ref":"#/components/schemas/TriagerInviteResponse"},"type":"array","title":"Invites"}},"type":"object","required":["total","page","per_page","invites"],"title":"TriagerInviteListResponse","description":"Paginated triager-invite list.\n\n``total`` / ``page`` / ``per_page`` are redeclared as **required**\nfor the same reason as :class:`OrganizationInviteListResponse`.\n\nAttributes:\n    total (int): Total invites matching the filters.\n    page (int): Current page number.\n    per_page (int): Page size.\n    invites (list[TriagerInviteResponse]): The page of invites."},"TriagerInviteResponse":{"properties":{"id":{"type":"integer","title":"Id"},"kind":{"$ref":"#/components/schemas/InviteKind"},"email":{"type":"string","title":"Email"},"status":{"$ref":"#/components/schemas/InviteStatus"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"},"redeemed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redeemed By User Id"},"expired":{"type":"boolean","title":"Expired"},"live":{"type":"boolean","title":"Live"}},"type":"object","required":["id","kind","email","status","created_at","expires_at","resolved_at","redeemed_by_user_id","expired","live"],"title":"TriagerInviteResponse","description":"A triager invite as admins see it (never the token).\n\nTriager invites carry no scope or grant role — the shared lifecycle\nfields are the whole shape."},"UpdateAssetRequest":{"properties":{"tier":{"anyOf":[{"$ref":"#/components/schemas/AssetTier"},{"type":"null"}]}},"type":"object","title":"UpdateAssetRequest","description":"Request body for asset updates.\n\nMatches api-3's ``UpdateAsset`` schema — currently only ``tier``\nis updatable through the public API.\n\nAttributes:\n    tier (AssetTier | None): New tier value."},"UpdateBeaconRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"allowed_cidrs":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Cidrs"},"dns_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dns Server"}},"type":"object","title":"UpdateBeaconRequest","description":"Request body for partially updating an organization beacon.\n\nAll fields are optional; only provided (non-``None``) fields are\napplied — matches ``BeaconProvisioningWorkflow.update_beacon``.\n\nAttributes:\n    name (str | None): New name, or ``None`` to leave unchanged.\n    allowed_cidrs (list[str] | None): New CIDRs, or ``None``.\n    dns_server (str | None): New DNS server, or ``None``."},"UpdateDefaultPentestDurationRequest":{"properties":{"default_pentest_duration_days":{"type":"integer","minimum":1.0,"title":"Default Pentest Duration Days"},"unit":{"type":"string","title":"Unit","description":"Unit of the duration. Only `days` is accepted.","default":"days"}},"type":"object","required":["default_pentest_duration_days"],"title":"UpdateDefaultPentestDurationRequest","description":"Request body for updating the default pentest duration.\n\nAttributes:\n    default_pentest_duration_days (int): New default pentest duration\n        in days. Must be at least 1. Also accepted as ``duration``.\n    unit (str): Duration unit. Only ``days`` is accepted."},"UpdateFindingStatusRequest":{"properties":{"status":{"$ref":"#/components/schemas/FindingStatus","description":"New lifecycle status"}},"type":"object","required":["status"],"title":"UpdateFindingStatusRequest","description":"Request body for updating a finding's status.\n\nAttributes:\n    status (FindingStatus): New lifecycle status (e.g. ``\"open\"``,\n        ``\"triaged\"``, ``\"fixed\"``). An integer status id is also\n        accepted — see :func:`status_from_legacy_id`."},"UpdateFindingStatusResponse":{"properties":{"result":{"type":"string","title":"Result","default":"success"},"finding_id":{"type":"string","title":"Finding Id","description":"Finding ID as hex string"}},"type":"object","required":["finding_id"],"title":"UpdateFindingStatusResponse","description":"Response from a successful finding status update.\n\nAttributes:\n    result (str): Operation result (``\"success\"``).\n    finding_id (str): Finding ID as hex string."},"UpdateLaunchRequest":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/LaunchStatus"},{"type":"null"}],"description":"New execution status."},"error_info":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Info","description":"Error message to record."},"start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time","description":"Execution start time."},"finish_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finish Time","description":"Execution end time."}},"type":"object","title":"UpdateLaunchRequest","description":"Request body for updating a launch.\n\nAll fields are optional — only provided fields are applied.\n\nAttributes:\n    status (LaunchStatus | None): New execution status.\n    error_info (str | None): Error message to record.\n    start_time (datetime.datetime | None): Execution start.\n    finish_time (datetime.datetime | None): Execution end."},"UserResponse":{"properties":{"uuid":{"type":"string","format":"uuid","title":"Uuid"},"username":{"type":"string","title":"Username"},"name":{"type":"string","title":"Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"pic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pic"},"picture":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture","description":"Previous name for pic.","deprecated":true,"readOnly":true}},"type":"object","required":["uuid","username","name","country","pic","picture"],"title":"UserResponse","description":"Public user profile.\n\nNo ``id``: the key exists in no compatible form. It carried an\ninteger, and ``users.id`` is a UUID with no integer counterpart —\nserving the UUID under the old name would answer a stored lookup\nwith a value that silently matches nothing, where an absent key\nfails at the read.\n\nThe multi-kind model has no single ``user_type``/``approved``/\n``onboarding`` at the user level (kinds live in ``user_kinds``,\napproval is per-kind), so this stays the minimal identity profile\napi-3's ``/me`` returned.\n\nAttributes:\n    uuid (UUID): The user's identifier (the ``users`` UUID PK).\n    username (str): Display username.\n    name (str): Full display name.\n    country (str | None): ISO country code.\n    pic (str | None): Profile picture URL. Also served as the\n        deprecated ``picture``."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerificationKind":{"type":"string","enum":["domain_checkbox","domain_dns","ip_ip","ip_checkbox"],"title":"VerificationKind","description":"Discriminator for the :class:`Verification` hierarchy.\n\nMembers:\n    DOMAIN_CHECKBOX: Domain self-attestation\n        (:class:`DomainCheckboxVerification`).\n    DOMAIN_DNS: Domain DNS-record proof\n        (:class:`DomainDNSVerification`).\n    IP_IP: CIDR proof (:class:`IpIpVerification`) — the requester's\n        source IP must fall within the verified CIDR\n        (system-activated).\n    IP_CHECKBOX: CIDR self-attestation\n        (:class:`IpCheckboxVerification`)."},"VisualizerDataResponse":{"properties":{"nodes":{"items":{"$ref":"#/components/schemas/VisualizerNode"},"type":"array","title":"Nodes"},"links":{"items":{"$ref":"#/components/schemas/VisualizerLink"},"type":"array","title":"Links"}},"type":"object","required":["nodes","links"],"title":"VisualizerDataResponse","description":"Domain hierarchy graph for organization visualization.\n\nAttributes:\n    nodes (list[VisualizerNode]): Domain/subdomain nodes.\n    links (list[VisualizerLink]): Parent-child edges."},"VisualizerLink":{"properties":{"source":{"type":"string","title":"Source"},"target":{"type":"string","title":"Target"}},"type":"object","required":["source","target"],"title":"VisualizerLink","description":"Edge in the domain hierarchy graph.\n\nConnects a parent domain node to one of its subdomains — the edge is\nemitted parent -> child, matching legacy. (An earlier version of this\ndocstring had the direction reversed; the code was always right.)\n\nAttributes:\n    source (str): Source node ID — the **parent** domain.\n    target (str): Target node ID — the **child** subdomain."},"VisualizerNode":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type","default":"domain"},"findings_count":{"type":"integer","title":"Findings Count","default":0},"risk_score":{"type":"integer","title":"Risk Score","default":1},"private":{"type":"boolean","title":"Private","default":false},"kev":{"type":"boolean","title":"Kev","default":false},"epss":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Epss"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"is_tld":{"type":"boolean","title":"Is Tld","default":false}},"type":"object","required":["id","label"],"title":"VisualizerNode","description":"Single node in the domain hierarchy graph.\n\nRepresents a domain or subdomain with aggregated finding counts\nand optional geolocation data.\n\nAttributes:\n    id (str): Node identifier (typically the domain value).\n    label (str): Display label.\n    type (str): Node type (``\"domain\"``, ``\"subdomain\"``, etc.).\n    findings_count (int): Number of findings for this node.\n    risk_score (int): Highest actionable severity on the asset —\n        ``3`` high, ``2`` medium, ``1`` low. Drives node colour, and\n        matches the asset's risk elsewhere in the product.\n    private (bool): Whether the asset is beacon-backed (internal),\n        which selects the private node type.\n    kev (bool): Whether any open finding on the asset cites a CVE in\n        the CISA KEV catalog.\n    epss (float | None): Highest EPSS score among the asset's open\n        findings; ``null`` when none carries one.\n    latitude (float | None): Geographic latitude.\n    longitude (float | None): Geographic longitude.\n    country (str | None): Country code.\n    is_tld (bool): Whether this is a top-level domain."}}}}