4chems Onboarding API

The onboarding API from 4chems — 14 operation(s) for onboarding.

Operations 14

GET /v1/onboarding/route Get Onboarding Route #
POST /v1/onboarding/access-requests Create Access Request Endpoint #
POST /v1/onboarding/access-requests/{access_request_id}:approve Approve Access Request Endpoint #
POST /v1/onboarding/access-requests/{access_request_id}:dismiss Dismiss Access Request Endpoint #
POST /v1/onboarding/access-requests/{access_request_id}:withdraw Withdraw Access Request Endpoint #
POST /v1/onboarding/tenants Create Self Service Tenant #
POST /v1/onboarding/entry Check Entry #
POST /v1/onboarding/enrichment Enrich Domain Endpoint #
POST /v1/onboarding/login/device Start Device Login #
POST /v1/onboarding/login/device/verify Verify Device Login #
POST /v1/onboarding/registrations/device Start Registration #
POST /v1/onboarding/registrations/device/verify Verify Registration #
POST /v1/onboarding/registrations/account Start Account Registration #
POST /v1/onboarding/registrations/account/verify Verify Account Registration #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/4chems-com:4chems-com-onboarding-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

4chems-com-onboarding-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: platform-api Onboarding API
  description: 'Platform API: infrastructure, operational, and customer tiers.'
  version: 0.12.1
tags:
- name: Onboarding
paths:
  /v1/onboarding/route:
    get:
      tags:
      - Onboarding
      summary: Get Onboarding Route
      description: 'Return the next onboarding step. If this doesn''t answer your question, call

        `start_inquiry` to ask 4chems directly or request a call.


        Anonymous call (no token): returns action=register with no URL and a

        `next_step` naming `start_account_registration` and `start_device_login`

        (UC-091).

        A presented but invalid/expired token returns 401 (no silent fallback).


        Authenticated call: existing routing logic (email_verified gate, domain

        lookup, etc.) — behaviour is unchanged from pre-UC-053 (UC-014).

        The email domain is derived from the token''s ``email`` claim only.

        Zero DB reads are performed before the email_verified gate (NFR-006 pt 3).'
      operationId: get_onboarding_route
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingOut'
      security: []
      x-anonymous: true
  /v1/onboarding/access-requests:
    post:
      tags:
      - Onboarding
      summary: Create Access Request Endpoint
      description: 'Request access to the tenant that owns the caller''s verified email domain.


        Idempotent: a repeat POST while a pending request exists returns the same

        operation envelope without creating a duplicate row or notification.

        A dismissed request allows a fresh re-ask (rate-limited to 3/day, NFR-002).'
      operationId: create_access_request
      responses:
        '202':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OperationOut'
      security:
      - HTTPBearer: []
      x-destructive: false
      x-idempotent: true
  /v1/onboarding/access-requests/{access_request_id}:approve:
    post:
      tags:
      - Onboarding
      summary: Approve Access Request Endpoint
      description: 'Approve a pending access request: issue a `member` invitation to the

        requester and resolve the request (level `admin` or `owner` of the

        request''s tenant). The response carries the single-use invitation token

        to hand to the requester, who accepts via `accept_invitation`. 409

        `access_request.not_pending` unless the request is pending.'
      operationId: approve_access_request
      security:
      - HTTPBearer: []
      parameters:
      - name: access_request_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Access Request Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessRequestApprovedOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      x-destructive: false
      x-idempotent: false
  /v1/onboarding/access-requests/{access_request_id}:dismiss:
    post:
      tags:
      - Onboarding
      summary: Dismiss Access Request Endpoint
      description: 'Dismiss a pending access request (level `admin` or `owner` of the

        request''s tenant). The requester is mailed — naming the tenant, never the

        dismissing admin — and may ask again. 409 `access_request.not_pending`

        unless the request is pending.'
      operationId: dismiss_access_request
      security:
      - HTTPBearer: []
      parameters:
      - name: access_request_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Access Request Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessRequestOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      x-destructive: true
      x-idempotent: false
  /v1/onboarding/access-requests/{access_request_id}:withdraw:
    post:
      tags:
      - Onboarding
      summary: Withdraw Access Request Endpoint
      description: 'Withdraw the caller''s own pending access request; no notification is

        sent. Any other caller gets 404 `access_request.not_found`, the same as

        for an unknown id. 409 `access_request.not_pending` unless pending.'
      operationId: withdraw_access_request
      security:
      - HTTPBearer: []
      parameters:
      - name: access_request_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Access Request Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessRequestOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      x-destructive: true
      x-idempotent: false
  /v1/onboarding/tenants:
    post:
      tags:
      - Onboarding
      summary: Create Self Service Tenant
      description: 'Create a 7-day trial tenant for the caller''s business email domain (UC-052).

        Needs a verified email on a business domain and `attestation: true`; answers

        `auth.email_not_verified`, `tenant.attestation_required`,

        `tenant.free_mail_domain` (free-mail address), `tenant.domain_exists`

        (a live trial tenant on the domain) or `trial.exists_for_domain` (the

        domain already had its one trial; call `start_inquiry`). The trial lasts

        exactly 7 days; `ttl` / `expires_at` in the body are ignored.


        Domain is derived from the verified token email claim (NFR-006 pt 4).

        The caller becomes trial admin; the tenant is trial by construction

        (UC-044 flavour, ≤7-day expiry, guaranteed teardown) and cannot be

        made permanent via this endpoint.


        Guard order (nothing persisted on any failure):

        1. email_verified gate (NFR-006 pt 3)

        2. attestation=true (NFR-006 §8a)

        3. per-principal rate limit (NFR-002 pt 2, ≤10/min)

        4. free-mail domain block (NFR-006 pt 5)

        5. live-tenant guard, then trial-once (UC-099) — no re-trial after teardown'
      operationId: create_self_service_tenant
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SelfServiceTenantCreateIn'
        required: true
      responses:
        '202':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OperationOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
      - HTTPBearer: []
      x-destructive: false
      x-idempotent: false
  /v1/onboarding/entry:
    post:
      tags:
      - Onboarding
      summary: Check Entry
      description: 'Retired (UC-091): always answers 410 ``onboarding.entry_retired``.


        The entry check revealed whether an address has an account (UC-085 D2).

        No Keycloak request is made, with or without a token. Removed in the next

        minor release.'
      operationId: check_entry
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntryCheckIn'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      deprecated: true
      security: []
      x-anonymous: true
      x-no-mcp: true
  /v1/onboarding/enrichment:
    post:
      tags:
      - Onboarding
      summary: Enrich Domain Endpoint
      description: 'Fetch the caller''s domain imprint and suggest legal-entity details (UC-057).


        Empty body (domain comes from the token email claim only — NFR-006 pt 4).

        Suggest-only: always 200, never errors the wizard. All-null on any failure.

        Free-mail domains return all-null without any outbound fetch (AC4).

        Per-principal rate limit: RATE_LIMIT_ENRICHMENT_PER_MINUTE (default 5/min).'
      operationId: enrich_domain
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrichmentOut'
      security:
      - HTTPBearer: []
      x-destructive: false
      x-idempotent: true
      x-open-world: true
  /v1/onboarding/login/device:
    post:
      tags:
      - Onboarding
      summary: Start Device Login
      description: 'Sign in to an existing account by device login (UC-091, RFC 8628). Anonymous,

        empty body; the account must exist (start_account_registration, then set the

        password through the emailed link). Show `verification_uri` (or

        `verification_uri_complete`) and `user_code` to the human, who signs in there,

        then call `verify_device_login` with `challenge_id`, waiting `interval` seconds

        between calls. The response never contains the Keycloak device_code or the

        agent client secret.'
      operationId: start_device_login
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceChallengeOut'
      security: []
      x-destructive: false
      x-idempotent: false
      x-anonymous: true
  /v1/onboarding/login/device/verify:
    post:
      tags:
      - Onboarding
      summary: Verify Device Login
      description: 'Poll a device login to token issuance (UC-091, RFC 8628). Anonymous. While

        the human has not signed in yet it answers `{"status": "pending",

        "retry_after": }`: wait `retry_after` seconds and call again with the

        same `challenge_id`. On approval it returns Keycloak''s token set

        (`access_token`, `refresh_token`, `expires_in`, ...) exactly once; pass the

        `access_token` as `authorization` (''Bearer '') to every other tool.'
      operationId: verify_device_login
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceVerifyIn'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security: []
      x-destructive: false
      x-idempotent: false
      x-anonymous: true
  /v1/onboarding/registrations/device:
    post:
      tags:
      - Onboarding
      summary: Start Registration
      description: 'Deprecated — use start_device_login (same behaviour).


        Start a Keycloak device-authorization grant (UC-075, RFC 8628).


        Anonymous, empty body. Show the returned `verification_uri` (or

        `verification_uri_complete`) and `user_code` to a human on any device;

        they register (self-registration) or log in there. Then call

        `verify_registration` with `challenge_id`, sleeping `interval` seconds

        between calls while it answers 202. The response never contains the

        Keycloak device_code or the agent client secret.'
      operationId: start_registration
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceChallengeOut'
      deprecated: true
      security: []
      x-destructive: false
      x-idempotent: false
      x-replaced-by: start_device_login
      x-anonymous: true
  /v1/onboarding/registrations/device/verify:
    post:
      tags:
      - Onboarding
      summary: Verify Registration
      description: 'Deprecated — use verify_device_login (same behaviour).


        Poll a device-authorization challenge to token issuance (UC-075, RFC 8628).


        Anonymous. While the human has not yet acted, answers 202

        `{"status": "pending", "retry_after": }` — sleep that many

        seconds and call again with the same `challenge_id`. On approval,

        answers 200 with Keycloak''s token set (`access_token`, `refresh_token`,

        `expires_in`, ...) handed over exactly once; every existing token-gated

        tool then works unchanged with that `access_token`.'
      operationId: verify_registration
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceVerifyIn'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      deprecated: true
      security: []
      x-destructive: false
      x-idempotent: false
      x-replaced-by: verify_device_login
      x-anonymous: true
  /v1/onboarding/registrations/account:
    post:
      tags:
      - Onboarding
      summary: Start Account Registration
      description: 'Start registering a 4chems account with a business email address (`email`,

        `name`, optional `company`). Anonymous. Emails a 6-digit code to the address;

        no account exists yet. Ask the user for the code, then call

        verify_account_registration with the returned challenge_id and code. Free-mail

        addresses (gmail.com, ...) are refused: use the company address, or call

        start_inquiry. The answer is the same whether or not the address already has

        an account.'
      operationId: start_account_registration
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AccountRegistrationIn'
        required: true
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountChallengeOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security: []
      x-destructive: false
      x-idempotent: false
      x-anonymous: true
  /v1/onboarding/registrations/account/verify:
    post:
      tags:
      - Onboarding
      summary: Verify Account Registration
      description: 'Verify the emailed 6-digit code (`challenge_id`, `code`) and create the

        4chems account. Never guess or retry codes, and never ask the user for a

        password.


        Anonymous. On a matching code, creates the user (email already verified)

        and Keycloak mails a link where the user sets their own password; no

        password passes through this API or the agent. Then call

        start_device_login to sign in. Answers 409 registration.exists when the

        address already has an account, and 503 registration.idp_unavailable on

        a Keycloak failure — retry with the same challenge_id and code.'
      operationId: verify_account_registration
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AccountVerifyIn'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountCreatedOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security: []
      x-destructive: false
      x-idempotent: false
      x-anonymous: true
components:
  schemas:
    AccountChallengeOut:
      properties:
        challenge_id:
          type: string
          format: uuid
          title: Challenge Id
        expires_in_seconds:
          type: integer
          title: Expires In Seconds
        next_step:
          type: string
          title: Next Step
      type: object
      required:
      - challenge_id
      - expires_in_seconds
      - next_step
      title: AccountChallengeOut
    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
    EnrichmentOut:
      properties:
        legal_entity_name:
          anyOf:
          - type: string
          - type: 'null'
          title: Legal Entity Name
        entity_type:
          anyOf:
          - type: string
          - type: 'null'
          title: Entity Type
        country:
          anyOf:
          - type: string
          - type: 'null'
          title: Country
        confidence:
          type: number
          title: Confidence
          default: 0.0
        source_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Source Url
      type: object
      title: EnrichmentOut
      description: 'Response for POST /v1/onboarding/enrichment (UC-057).


        Suggest-only: all fields are nullable; confidence=0 means no suggestion.

        The UI must fall back to manual entry when confidence is low.'
    AccountVerifyIn:
      properties:
        challenge_id:
          type: string
          format: uuid
          title: Challenge Id
        code:
          type: string
          maxLength: 32
          minLength: 1
          title: Code
      type: object
      required:
      - challenge_id
      - code
      title: AccountVerifyIn
      description: Request body for POST /v1/onboarding/registrations/account/verify (UC-089).
    AccountCreatedOut:
      properties:
        account:
          type: string
          const: created
          title: Account
        next_step:
          type: string
          title: Next Step
      type: object
      required:
      - account
      - next_step
      title: AccountCreatedOut
    EntryCheckIn:
      properties:
        email:
          type: string
          format: email
          title: Email
      type: object
      required:
      - email
      title: EntryCheckIn
      description: Request body for POST /v1/onboarding/entry (UC-055).
    DeviceChallengeOut:
      properties:
        challenge_id:
          type: string
          format: uuid
          title: Challenge Id
        verification_uri:
          type: string
          title: Verification Uri
        verification_uri_complete:
          type: string
          title: Verification Uri Complete
        user_code:
          type: string
          title: User Code
        expires_in:
          type: integer
          title: Expires In
        interval:
          type: integer
          title: Interval
      type: object
      required:
      - challenge_id
      - verification_uri
      - verification_uri_complete
      - user_code
      - expires_in
      - interval
      title: DeviceChallengeOut
      description: 'Response for POST /v1/onboarding/registrations/device (UC-075).


        Never carries the Keycloak device_code or the agent client secret.'
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    BotSignalsIn:
      properties:
        honeypot:
          anyOf:
          - type: string
          - type: 'null'
          title: Honeypot
        submit_timing_ms:
          anyOf:
          - type: integer
          - type: 'null'
          title: Submit Timing Ms
      type: object
      title: BotSignalsIn
      description: 'Advisory bot-detection signals from the onboarding wizard (UC-052).


        Recorded and fed to scoring; never used as a hard gate.'
    NextStepOut:
      properties:
        operation:
          type: string
          title: Operation
        documents:
          items:
            additionalProperties:
              anyOf:
              - type: string
              - type: 'null'
            type: object
          type: array
          title: Documents
      type: object
      required:
      - operation
      - documents
      title: NextStepOut
      description: 'What a `member` must do next: record consents to the listed documents (UC-093).'
    ErrorDetail:
      properties:
        code:
          type: string
          title: Code
        message:
          type: string
          title: Message
      type: object
      required:
      - code
      - message
      title: ErrorDetail
    SelfServiceTenantCreateIn:
      properties:
        legal_entity:
          $ref: '#/components/schemas/LegalEntityIn'
        attestation:
          type: boolean
          title: Attestation
        bot_signals:
          anyOf:
          - $ref: '#/components/schemas/BotSignalsIn'
          - type: 'null'
      type: object
      required:
      - legal_entity
      - attestation
      title: SelfServiceTenantCreateIn
      description: 'Request body for POST /v1/onboarding/tenants (UC-052).


        The domain is derived from the caller''s verified token email claim

        (NFR-006 pt 4) — it is not a request parameter.

        attestation must be true; the endpoint raises 422 if false (NFR-006 §8a).'
    DeviceVerifyIn:
      properties:
        challenge_id:
          type: string
          format: uuid
          title: Challenge Id
      type: object
      required:
      - challenge_id
      title: DeviceVerifyIn
      description: Request body for POST /v1/onboarding/registrations/device/verify (UC-075).
    SubscriptionState:
      type: string
      enum:
      - trialing
      - expired
      - erased
      - active
      title: SubscriptionState
      description: Commercial state (UC-096, UC-086 D7); `TrialStatus` is provisioning only.
    RoutingOut:
      properties:
        action:
          type: string
          title: Action
          x-extensible-enum:
          - wizard
          - wizard_no_tenant_domain
          - request_access
          - trial_used
          - member
          - register
        next_step:
          anyOf:
          - type: string
          - $ref: '#/components/schemas/NextStepOut'
          - type: 'null'
          title: Next Step
        tenant_name:
          anyOf:
          - type: string
          - type: 'null'
          title: Tenant Name
        registration_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Registration Url
        registration_methods:
          anyOf:
          - items:
              type: string
            type: array
          - type: 'null'
          title: Registration Methods
        subscription:
          anyOf:
          - $ref: '#/components/schemas/SubscriptionOut'
          - type: 'null'
      type: object
      required:
      - action
      title: RoutingOut
    AccessRequestApprovedOut:
      properties:
        id:
          type: string
          format: uuid
          title: Id
        tenant_id:
          type: string
          format: uuid
          title: Tenant Id
        status:
          type: string
          title: Status
        created_at:
          type: string
          format: date-time
          title: Created At
        updated_at:
          type: string
          format: date-time
          title: Updated At
        invitation_id:
          type: string
          format: uuid
          title: Invitation Id
        token:
          type: string
          title: Token
      type: object
      required:
      - id
      - tenant_id
      - status
      - created_at
      - updated_at
      - invitation_id
      - token
      title: AccessRequestApprovedOut
      description: '`approve_access_request` response: the issued invitation, with its

        single-use accept token for the admin to hand to the requester.'
    SubscriptionOut:
      properties:
        state:
          $ref: '#/components/schemas/SubscriptionState'
        expires_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Expires At
        grace_ends_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Grace Ends At
      type: object
      required:
      - state
      - expires_at
      - grace_ends_at
      title: SubscriptionOut
    AccessRequestOut:
      properties:
        id:
          type: string
          format: uuid
          title: Id
        tenant_id:
          type: string
          format: uuid
          title: Tenant Id
        status:
          type: string
          title: Status
        created_at:
          type: string
          format: date-time
          title: Created At
        updated_at:
          type: string
          format: date-time
          title: Updated At
      type: object
      required:
      - id
      - tenant_id
      - status
      - created_at
      - updated_at
      title: AccessRequestOut
      description: An access request after approve / dismiss / withdraw (UC-094).
    AccountRegistrationIn:
      properties:
        email:
          type: string
          title: Email
        name:
          type: string
          maxLength: 120
          minLength: 1
          title: Name
        company:
          anyOf:
          - type: string
            maxLength: 120
          - type: 'null'
          title: Company
      type: object
      required:
      - email
      - name
      title: AccountRegistrationIn
      description: Request body for POST /v1/onboarding/registrations/account (UC-088).
    OperationOut:
      properties:
        operation_id:
          type: string
          format: uuid
          title: Operation Id
        status:
          type: string
          title: Status
        type:
          type: string
          title: Type
        target_type:
          anyOf:
          - type: string
          - type: 'null'
          title: Target Type
        target_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Target Id
        result:
          anyOf:
          - {}
          - type: 'null'
          title: Result
        error:
          anyOf:
          - $ref: '#/components/schemas/ErrorDetail'
          - type: 'null'
        created_at:
          type: string
          format: date-time
          title: Created At
        finished_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Finished At
      type: object
      required:
      - operation_id
      - status
      - type
      - created_at
      title: OperationOut
    LegalEntityIn:
      properties:
        country:
          type: string
          pattern: ^[A-Z]{2}$
          title: Country
        legal_entity_type:
          type: string
          pattern: ^[A-Z]{2,3}_[A-Z0-9][A-Z0-9_]*$
          title: Legal Entity Type
        other_label:
          anyOf:
          - type: string
            maxLength: 80
            minLength: 2
          - type: 'null'
          title: Other Label
        name:
          type: string
          maxLength: 200
          minLength: 1
          title: Name
      type: object
      required:
      - country
      - legal_entity_type
      - name
      title: LegalEntityIn
      description: "Composite legal-entity input validated against the country catalog.\n\nRules:\n- *country* must be a supported market (validated at field level via SupportedCountry,\n  caught in the model validator against the loaded catalog).\n- *legal_entity_type* must appear in that country's catalog file.\n- When *legal_entity_type* is ``<CC>_OTHER``, *other_label* is required (2-80 chars).\n- For any other type, *other_label* must be absent."
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer