openapi: 3.0.0
info:
  title: Telkor API
  description: |
    Complete API reference for the Telkor API — coverage and plan management, SIM inventory, link lifecycle, bulk operations, and organisation account data.

    ## Quick Start

    1. Get your `client_id` and `client_secret` from [OmaxTelecom Console](https://console.omaxtelecom.com)
    2. Run **Auth → Get Access Token** to obtain a Bearer token
    3. All other endpoints use the `access_token` in the `Authorization` header

    ## Authentication (OAuth 2.0)

    This API uses the **OAuth 2.0 Client Credentials** grant type ([RFC 6749 §4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)).

    **Token endpoint:**

    ```
    POST https://api.omaxtelecom.com/auth/token
    ```

    | Parameter | Value |
    |-----------|-------|
    | `grant_type` | `client_credentials` |
    | `client_id` | Your Client ID |
    | `client_secret` | Your Client Secret |

    The token endpoint returns a Bearer `access_token` with a limited lifetime (`expires_in` seconds). When the token expires, request a new one — there are no refresh tokens in this flow.

    ## API Design

    The API follows RESTful conventions:

    - **Path parameters** identify resources (`coverage_id`, `sim_id`, `plan_id`, `link_id`, etc.)
    - **Query parameters** are used for filtering, pagination, and search
    - All data is scoped to your organisation (tenant)
    - **Bulk operations** return a task id — poll `GET /tasks/{task_id}` for progress

    ### Common Patterns

    - **List**: `GET /resource` — returns a paginated list
    - **Single**: `GET /resource/{id}` — returns a single item
    - **Create**: `POST /resource` — creates a new resource
    - **Update**: `PATCH /resource/{id}` — partial update
    - **Delete**: `DELETE /resource/{id}` — soft-delete (returns 204)
    - **Bulk**: `POST /resource/bulk-*` — async bulk job (returns task)

    ### Pagination

    List endpoints return paginated results:

    ```json
    {
      "items": [...],
      "total": 42,
      "page": 1,
      "page_size": 25,
      "total_pages": 2
    }
    ```

    ### Error Format

    ```json
    {
      "message": "Entity not found: id=...",
      "code": "entity_not_found",
      "details": { ... }
    }
    ```

    ### Error Codes

    | Code | HTTP | Description |
    |------|------|-------------|
    | `unauthorized` | 401 | Missing or invalid token |
    | `forbidden` | 403 | Authenticated but lacks required role |
    | `entity_not_found` | 404 | Resource not found |
    | `entity_deleted` | 410 | Resource has been soft-deleted |
    | `duplicate_entity` | 409 | Entity already exists |
    | `validation_error` | 422 | Business rule violation |
    | `request_validation_error` | 422 | Request input failed schema validation |
    | `upstream_unavailable` | 502 | Upstream dependency error |
  version: 1.0.0
components:
  securitySchemes:
    noauthAuth:
      type: http
      scheme: noauth
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    ActivationCreateRequest:
      properties:
        plan_id:
          type: string
          format: uuid
          title: Plan Id
          description: ID of the plan to activate on the link.
      type: object
      required:
      - plan_id
      title: ActivationCreateRequest
      description: Request body for `POST /links/{link_id}/activations`.
      examples:
      - plan_id: b14d2a91-1111-2222-3333-444455556666
    ActivationDataUsageSummaryResponse-Output:
      properties:
        allocated_mib:
          type: string
          pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          title: Allocated Mib
          description: Total MiB allocated to the activation when it started.
        remaining_mib:
          type: string
          pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          title: Remaining Mib
          description: MiB still available on the activation.
        used_mib:
          type: string
          pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          title: Used Mib
          description: MiB consumed so far (allocated - remaining).
      type: object
      required:
      - allocated_mib
      - remaining_mib
      - used_mib
      title: ActivationDataUsageSummaryResponse
      description: Data usage summary for a single activation.
      examples:
      - allocated_mib: '1024.00'
        remaining_mib: '812.55'
        used_mib: '211.45'
    ActivationPlanSummary:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal Plan identifier.
        name:
          type: string
          title: Name
          description: Customer-assigned Plan name.
        included_data_mb:
          type: integer
          minimum: 0
          title: Included Data Mb
          description: Bundled data, in MB.
        validity_days:
          type: integer
          minimum: 1
          title: Validity Days
          description: Validity, in days.
      type: object
      required:
      - id
      - name
      - included_data_mb
      - validity_days
      title: ActivationPlanSummary
      description: |-
        Minimal plan info embedded on `ActivationResponse`.

        Just enough to make sense of the activation without forcing a
        follow-up request to `/plans/{id}` — id, name, and the two
        customer-relevant numbers (bundled data, validity).
      examples:
      - id: b14d2a91-1111-2222-3333-444455556666
        included_data_mb: 1024
        name: Roaming Pilot 1 GB / 30d
        validity_days: 30
    ActivationResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal Activation identifier.
        plan:
          $ref: '#/components/schemas/ActivationPlanSummary'
          description: The plan this activation is an instance of.
        status:
          $ref: '#/components/schemas/ActivationStatus'
          description: Runtime status of the activation.
        source:
          $ref: '#/components/schemas/ActivationSource'
          description: 'How the activation was created: `manual` (customer-initiated) or `resubscription` (automatic).'
        activated_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Activated At
          description: UTC timestamp when the activation became active. Null until activation completes.
        expires_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Expires At
          description: UTC timestamp when the activation is scheduled to expire. Null when no fixed end is set.
        usage:
          $ref: '#/components/schemas/ActivationDataUsageSummaryResponse-Output'
          description: Aggregated data usage for the activation.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the activation was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update.
      type: object
      required:
      - id
      - plan
      - status
      - source
      - usage
      - created_at
      - updated_at
      title: ActivationResponse
      description: |-
        A plan activation on a link, as visible to the caller's organisation.

        Hides internal correlation/sync fields (`provider_id`, `synced_at`).
        `usage` is zero-valued until the background usage-collection worker
        takes its first snapshot for the activation.
      examples:
      - activated_at: 2026-06-09 08:00:00+00:00
        created_at: 2026-06-09 08:00:00+00:00
        expires_at: 2026-07-09 08:00:00+00:00
        id: 5fa1e2b8-aaaa-bbbb-cccc-ddddeeeeffff
        plan:
          id: b14d2a91-1111-2222-3333-444455556666
          included_data_mb: 1024
          name: Roaming Pilot 1 GB / 30d
          validity_days: 30
        source: manual
        status: active
        updated_at: 2026-06-09 08:15:00+00:00
        usage:
          allocated_mib: '1024.00'
          remaining_mib: '812.55'
          used_mib: '211.45'
    ActivationSortField:
      type: string
      enum:
      - created_at
      - updated_at
      - status
      - activated_at
      - expires_at
      title: ActivationSortField
      description: |-
        Customer-visible sort fields for the activation list endpoint.

        Mirrors the domain enum 1:1 — no internal sync timestamps to hide
        on this entity.
    ActivationSource:
      type: string
      enum:
      - manual
      - resubscription
      title: ActivationSource
      description: How the activation was created.
    ActivationStatus:
      type: string
      enum:
      - active
      - expired
      - exhausted
      - removed
      title: ActivationStatus
      description: Runtime status of a plan activation on a link.
    ActivationType:
      type: string
      enum:
      - immediate
      - on_first_data_usage
      title: ActivationType
      description: When the plan activates after being assigned to a link.
    BulkTaskItemResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal identifier for this item.
        reference_id:
          type: string
          title: Reference Id
          description: The per-action target identifier the worker used for this item (SIM iccid, link id, etc.). Lets you
            correlate failures back to your input list.
        status:
          $ref: '#/components/schemas/BulkTaskItemStatus'
          description: Per-item lifecycle status.
        payload:
          additionalProperties: true
          type: object
          title: Payload
          description: The input the worker received for this item.
        result:
          anyOf:
          - additionalProperties: true
            type: object
          - type: 'null'
          title: Result
          description: Worker output for successful items. Null for non-`success` statuses.
        error:
          anyOf:
          - type: string
          - type: 'null'
          title: Error
          description: Failure reason for `failed` items. Null otherwise.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the item was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update.
      type: object
      required:
      - id
      - reference_id
      - status
      - payload
      - created_at
      - updated_at
      title: BulkTaskItemResponse
      description: |-
        A single item within a bulk task — one input/outcome pair.

        `reference_id` is the per-action target identifier the worker used
        (e.g. a SIM iccid, a link UUID-as-string), letting the customer
        correlate items back to their input list. `payload` is the input
        the worker received for this item; `result` and `error` are the
        outcome side — exactly one is populated for terminal items
        (`success` → `result`, `failed` → `error`).
      examples:
      - created_at: 2026-06-09 08:00:00+00:00
        id: 11ab2c30-1111-2222-3333-444455556666
        payload:
          link_id: 9b8d4f10-2222-3333-4444-555566667777
        reference_id: 9b8d4f10-2222-3333-4444-555566667777
        result:
          deleted: true
        status: success
        updated_at: 2026-06-09 08:05:00+00:00
      - created_at: 2026-06-09 08:00:00+00:00
        error: SIM is already linked to another link
        id: 22cd4e50-1111-2222-3333-444455556666
        payload:
          iccid: '8949000123456789012'
        reference_id: '8949000123456789012'
        status: failed
        updated_at: 2026-06-09 08:05:00+00:00
    BulkTaskItemSortField:
      type: string
      enum:
      - created_at
      - updated_at
      - status
      title: BulkTaskItemSortField
      description: Customer-visible sort fields for the items list endpoint.
    BulkTaskItemStatus:
      type: string
      enum:
      - pending
      - running
      - success
      - failed
      title: BulkTaskItemStatus
      description: Status of a single item within a bulk task.
    BulkTaskResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal bulk-task identifier.
        type:
          $ref: '#/components/schemas/BulkTaskType'
          description: What bulk operation this task represents.
        status:
          $ref: '#/components/schemas/BulkTaskStatus'
          description: 'Lifecycle status of the task itself: `pending` (queued), `running` (worker picked it up), `completed`
            (all items processed — some may still have failed; check counters), `failed` (worker job crashed).'
        total_items:
          type: integer
          minimum: 0
          title: Total Items
          description: Total number of items the task was created with.
        completed_items:
          type: integer
          minimum: 0
          title: Completed Items
          description: Items processed successfully.
        failed_items:
          type: integer
          minimum: 0
          title: Failed Items
          description: Items that the worker could not process.
        pending_items:
          type: integer
          minimum: 0
          title: Pending Items
          description: Items still waiting to be processed (`total - completed - failed`).
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the task was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update.
      type: object
      required:
      - id
      - type
      - status
      - total_items
      - completed_items
      - failed_items
      - pending_items
      - created_at
      - updated_at
      title: BulkTaskResponse
      description: |-
        A bulk task header as visible to the caller's organisation.

        `pending_items` is derived (`total - completed - failed`) so the
        customer can poll a single counter to know when work is finished.
        Items are NOT included here — fetch via `GET /tasks/{id}/items`.
      examples:
      - completed_items: 42
        created_at: 2026-06-09 08:00:00+00:00
        failed_items: 3
        id: 7c2a9d10-aaaa-bbbb-cccc-ddddeeeeffff
        pending_items: 55
        status: running
        total_items: 100
        type: link_delete
        updated_at: 2026-06-09 08:05:30+00:00
    BulkTaskSortField:
      type: string
      enum:
      - created_at
      - updated_at
      - status
      title: BulkTaskSortField
      description: Customer-visible sort fields for the task list endpoint.
    BulkTaskStatus:
      type: string
      enum:
      - pending
      - running
      - completed
      - failed
      title: BulkTaskStatus
      description: Status of a bulk task.
    BulkTaskType:
      type: string
      enum:
      - sim_provisioning
      - link_delink_sim
      - link_delete
      - link_suspend
      - link_resume
      - link_add_activation
      - link_delete_activation
      title: BulkTaskType
      description: Type of bulk operation.
    CallerOrganizationResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal identifier for your organisation. Useful when contacting support.
          examples:
          - 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        name:
          type: string
          title: Name
          description: Human-readable display name.
          examples:
          - Speakeasy
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp the organisation was created.
          examples:
          - 2026-01-15 08:30:00+00:00
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp the organisation was last updated.
          examples:
          - 2026-04-20 11:42:00+00:00
      type: object
      required:
      - id
      - name
      - created_at
      - updated_at
      title: CallerOrganizationResponse
      description: |-
        Customer-visible view of the caller's own organisation.

        Returned by `GET /org`. Trimmed down from the superadmin
        `OrganizationResponse`: `keycloak_org_id` and the `settings`
        block (provider integration config) are hidden — neither is
        actionable from the customer side.
      examples:
      - created_at: 2026-01-15 08:30:00+00:00
        id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        name: Speakeasy
        updated_at: 2026-04-20 11:42:00+00:00
    CompatibilityZone:
      type: string
      enum:
      - magenta
      - cyan
      - brown
      - amber
      - violet
      - coral
      - teal
      - slate
      - ivory
      title: CompatibilityZone
      description: User-friendly compatibility zone identifiers.
    ConnectedRAT:
      type: string
      enum:
      - 2g_3g
      - 4g
      - 5g
      - unknown
      title: ConnectedRAT
      description: RAT type reported in live network connectivity status.
    CostServiceName:
      type: string
      enum:
      - DATA
      - SMS-MO
      - SMS-MT
      - IOT
      title: CostServiceName
      description: Service categories that usage is billed against.
    CostUsageLineResponse-Output:
      properties:
        service_name:
          anyOf:
          - $ref: '#/components/schemas/CostServiceName'
          - type: 'null'
          description: Service category (`DATA` / `SMS-MO` / `SMS-MT` / `IOT`). May be null for lines the provider didn't
            categorise.
        usage:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Usage
          description: Physical quantity (MB for DATA, count for SMS/IOT).
          examples:
          - '1024.50'
        amount:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Amount
          description: Money in the snapshot's `currency`.
          examples:
          - '12.34'
        operators:
          items:
            $ref: '#/components/schemas/OperatorSummary'
          type: array
          title: Operators
          description: Operators this usage was attributed to. Empty when no mapping was possible.
      type: object
      title: CostUsageLineResponse
      description: |-
        A single usage line from a monthly cost snapshot.

        `usage` is a physical quantity (MB for `DATA`, count for `SMS-MO` /
        `SMS-MT` / `IOT`). `amount` is always in the snapshot's `currency`
        (€ today). `operators` is the set of our operators this usage was
        attributed to at collection time — may be empty if the collector
        could not map the provider's keys to any of the org's operators.
      examples:
      - amount: '12.34'
        operators:
        - compatibility_zone: magenta
          country: DEU
          id: 0a1b2c3d-1111-2222-3333-444455556666
          name: Vodafone DE
          tadig: DEUD2
        service_name: DATA
        usage: '1024.50'
    CoverageCreateRequest:
      properties:
        name:
          type: string
          minLength: 1
          title: Name
          description: Display name for the coverage. Not required to be unique.
          examples:
          - EU + UK
        operator_ids:
          items:
            type: string
            format: uuid
          type: array
          minItems: 1
          title: Operator Ids
          description: Operator ids to include in the coverage. Must be non-empty.
      type: object
      required:
      - name
      - operator_ids
      title: CoverageCreateRequest
      description: |-
        Create a new coverage by naming a set of operators.

        All operators must belong to the same compatibility zone family
        (i.e. resolve to the same provider) — mixed-provider coverages are
        rejected with 422. Operators must already exist in the caller's
        catalogue; unknown ids return 404. Names are not constrained to be
        unique — pick any label that helps you identify the coverage.
      examples:
      - name: EU + UK
        operator_ids:
        - 0a1b2c3d-1111-2222-3333-444455556666
        - 1b2c3d4e-2222-3333-4444-555566667777
    CoverageOperatorsRequest:
      properties:
        operator_ids:
          items:
            type: string
            format: uuid
          type: array
          minItems: 1
          title: Operator Ids
          description: Operator ids to add or remove. Must be non-empty.
      type: object
      required:
      - operator_ids
      title: CoverageOperatorsRequest
      description: |-
        Add or remove operators from an existing coverage.

        Shared by `POST /coverages/{id}/operators` and
        `DELETE /coverages/{id}/operators` — same wire shape, different
        semantics. Both endpoints are **idempotent**: ids already present
        (on add) or already absent (on remove) are silently skipped.

        Operators must already exist in the caller's catalogue; unknown ids
        return 404. Adding operators from a different compatibility-zone
        family than the coverage already spans is rejected with 422.
        Removing all operators is rejected with 422.
      examples:
      - operator_ids:
        - 0a1b2c3d-1111-2222-3333-444455556666
        - 1b2c3d4e-2222-3333-4444-555566667777
    CoverageRenameRequest:
      properties:
        name:
          type: string
          minLength: 1
          title: Name
          description: New display name for the coverage. Not required to be unique.
          examples:
          - EU + UK (renamed)
      type: object
      required:
      - name
      title: CoverageRenameRequest
      description: |-
        Rename an existing coverage (local-only change).

        The coverage's `name` is stored in our DB and used everywhere the
        customer sees it. The provider's synthesized id is unaffected.
        Names are not constrained to be unique — pick any label that helps
        you identify the coverage in your UI.
      examples:
      - name: EU + UK (renamed)
    CoverageResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal coverage identifier.
        name:
          type: string
          title: Name
          description: Coverage display name.
          examples:
          - EU + UK
        compatibility_zones:
          items:
            $ref: '#/components/schemas/CompatibilityZone'
          type: array
          title: Compatibility Zones
          description: Compatibility zones spanned by the operators in this coverage.
        operators:
          items:
            $ref: '#/components/schemas/OperatorSummary'
          type: array
          title: Operators
          description: Operators included in this coverage.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the coverage was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update.
      type: object
      required:
      - id
      - name
      - created_at
      - updated_at
      title: CoverageResponse
      description: |-
        A coverage zone as visible to the caller's organisation.

        `compatibility_zones` is computed from the embedded operators (not
        stored) — it tells the caller which provider ecosystems this
        coverage spans. `provider_id` is intentionally hidden: it's an
        internal correlation id and the user-facing `name` is what the
        customer assigned.
      examples:
      - compatibility_zones:
        - magenta
        created_at: 2026-05-01 10:00:00+00:00
        id: f47ac10b-58cc-4372-a567-0e02b2c3d479
        name: EU + UK
        operators:
        - compatibility_zone: magenta
          country: DEU
          id: 0a1b2c3d-1111-2222-3333-444455556666
          name: Vodafone DE
          tadig: DEUD2
        updated_at: 2026-05-20 14:30:00+00:00
    CoverageSortField:
      type: string
      enum:
      - name
      - created_at
      - updated_at
      title: CoverageSortField
      description: |-
        Customer-visible sort fields for the coverage list endpoint.

        Subset of the domain sort fields — only fields actually surfaced on
        `CoverageResponse` are sortable. Sorting by hidden fields would
        confuse clients that can't see the resulting order. `status` is
        excluded because customer-facing responses are always `active`
        (removed coverages are filtered from list and 410 from get).
    CoverageSummary:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal coverage identifier.
        name:
          type: string
          title: Name
          description: Coverage display name.
          examples:
          - EU + UK
        compatibility_zones:
          items:
            $ref: '#/components/schemas/CompatibilityZone'
          type: array
          title: Compatibility Zones
          description: Compatibility zones spanned by the coverage.
      type: object
      required:
      - id
      - name
      title: CoverageSummary
      description: |-
        Minimal coverage info embedded on other resources' responses.

        Enough to identify a coverage without bloating the host payload
        with the full operator list. Used on `PlanResponse` so a customer
        can see which coverage a plan applies to without a second request.
      examples:
      - compatibility_zones:
        - magenta
        id: f47ac10b-58cc-4372-a567-0e02b2c3d479
        name: EU + UK
    DataCapSchema:
      properties:
        threshold_mb:
          type: integer
          minimum: 0
          title: Threshold Mb
          description: Usage threshold in MB.
        period:
          $ref: '#/components/schemas/LimitPeriod'
          description: Base period (daily or monthly).
        period_multiplier:
          type: integer
          minimum: 1
          title: Period Multiplier
          description: Number of base periods between resets.
        action:
          $ref: '#/components/schemas/LimitAction'
          description: Action applied at threshold.
      type: object
      required:
      - threshold_mb
      - period
      - period_multiplier
      - action
      title: DataCapSchema
      description: |-
        Wire shape for a plan's data cap.

        A cap fires when usage hits `threshold_mb` within the period defined
        by `period` * `period_multiplier` (e.g. period=monthly,
        period_multiplier=1 = once a month). `action` is what happens at
        threshold — currently always a throttle-down to a fixed speed.
      examples:
      - action: throttle_256kbps
        period: monthly
        period_multiplier: 1
        threshold_mb: 1024
    ESimProfileStatus:
      type: string
      enum:
      - installed
      - downloaded
      - enabled
      - disabled
      - deleted
      - error
      title: ESimProfileStatus
      description: eSIM profile status.
    ErrorResponse:
      description: |-
        Standard error response body.

        Three fields with clear, non-overlapping roles:

        - `message` — human-readable description (read by humans skimming logs / UIs).
        - `code` — machine-readable identifier for the failure class (e.g.
          `entity_not_found`, `upstream_unavailable`). Clients branch on this.
        - `details` — optional structured machine-readable context (e.g. the
          entity name and identifiers for a 404), so clients don't have to
          regex-parse `message` to act on it.
      examples:
      - code: entity_not_found
        details:
          entity: Entity
          identifiers:
            id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
      - code: validation_error
        message: Operation not allowed in current state
      - code: upstream_unavailable
        message: Internal server error
      properties:
        message:
          description: Human-readable description. For 5xx errors this is a generic message; the real cause is in our server
            logs.
          title: Message
          type: string
        code:
          description: Stable machine-readable identifier for the failure class (e.g. `entity_not_found`, `validation_error`,
            `upstream_unavailable`).
          title: Code
          type: string
        details:
          anyOf:
          - additionalProperties: true
            type: object
          - type: 'null'
          default: null
          description: Structured context for the error, when available (e.g. `{entity, identifiers}` for not-found / duplicate
            cases). Absent for 5xx responses.
          title: Details
      required:
      - message
      - code
      title: ErrorResponse
      type: object
    LimitAction:
      type: string
      enum:
      - throttle_256kbps
      - throttle_384kbps
      - throttle_512kbps
      - throttle_1mbps
      - throttle_2mbps
      - throttle_5mbps
      - throttle_10mbps
      - throttle_20mbps
      title: LimitAction
      description: Action when data cap threshold is exceeded.
    LimitPeriod:
      type: string
      enum:
      - daily
      - monthly
      title: LimitPeriod
      description: Base time window for data cap reset.
    LinkAttachSIMRequest:
      properties:
        sim_id:
          type: string
          format: uuid
          title: Sim Id
          description: ID of the SIM to bind to the link.
      type: object
      required:
      - sim_id
      title: LinkAttachSIMRequest
      description: Request body for `POST /links/{id}/sim` — attach a SIM to a link.
      examples:
      - sim_id: f47ac10b-58cc-4372-a567-0e02b2c3d479
    LinkBulkActivationRequest:
      properties:
        link_ids:
          items:
            type: string
            format: uuid
          type: array
          maxItems: 5000
          minItems: 1
          title: Link Ids
          description: Links to act on. Must be non-empty; duplicates are rejected.
        plan_id:
          type: string
          format: uuid
          title: Plan Id
          description: Plan to activate (or whose activations to remove) on each link.
      type: object
      required:
      - link_ids
      - plan_id
      title: LinkBulkActivationRequest
      description: |-
        Shared payload for the activation-targeted bulk-link actions.

        Used by `bulk-add-activation` (activate plan X on each link) and
        `bulk-delete-activation` (remove all active activations of plan X
        from each link). The bulk delete-activation deliberately targets
        activations by **plan**, not by activation id — in bulk you
        typically know "drop plan X from these N links", not the per-link
        activation UUIDs.
      examples:
      - link_ids:
        - 9b8d4f10-2222-3333-4444-555566667777
        - 9b8d4f10-2222-3333-4444-888899990000
        plan_id: b14d2a91-1111-2222-3333-444455556666
    LinkBulkRequest:
      properties:
        link_ids:
          items:
            type: string
            format: uuid
          type: array
          maxItems: 5000
          minItems: 1
          title: Link Ids
          description: Links to act on. Must be non-empty; duplicates are rejected.
      type: object
      required:
      - link_ids
      title: LinkBulkRequest
      description: |-
        Shared payload for the simpler bulk-link actions.

        Used by `bulk-delink-sim`, `bulk-delete`, `bulk-suspend`, and
        `bulk-resume` — all four operate on a flat list of link ids with
        no per-item parameters. Per-item failures (state mismatch, missing
        SIM, upstream rejection) are recorded against the offending item
        only and the task keeps processing the rest.
      examples:
      - link_ids:
        - 9b8d4f10-2222-3333-4444-555566667777
        - 9b8d4f10-2222-3333-4444-888899990000
    LinkCreateRequest:
      properties:
        name:
          type: string
          maxLength: 100
          minLength: 1
          title: Name
          description: Customer-assigned name for the Link.
        sim_id:
          type: string
          format: uuid
          title: Sim Id
          description: ID of the SIM to bind to the Link.
        plan_ids:
          anyOf:
          - items:
              type: string
              format: uuid
            type: array
          - type: 'null'
          title: Plan Ids
          description: Plans to activate on the Link at creation. Omit to create a pre-active Link.
      type: object
      required:
      - name
      - sim_id
      title: LinkCreateRequest
      description: |-
        Request body for `POST /links`.

        A SIM is required at creation — it determines the provider the Link
        belongs to and binds the two together in one call. `plan_ids`, when
        supplied, activates the Link immediately with those plans; omitting
        them creates a Link in `pre_active` waiting for first activation.
      examples:
      - name: Berlin-IoT-001
        plan_ids:
        - b14d2a91-1111-2222-3333-444455556666
        sim_id: f47ac10b-58cc-4372-a567-0e02b2c3d479
    LinkDataUsageSummaryResponse-Output:
      properties:
        total_used_mib:
          type: string
          pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          title: Total Used Mib
          description: Total MiB used across all activations on the link.
        activation_count:
          type: integer
          minimum: 0
          title: Activation Count
          description: Number of activations contributing to the total.
      type: object
      required:
      - total_used_mib
      - activation_count
      title: LinkDataUsageSummaryResponse
      description: Aggregated data usage across all activations on a link.
      examples:
      - activation_count: 3
        total_used_mib: '1234.56'
    LinkNetworkBarsRequest:
      properties:
        network_types:
          items:
            $ref: '#/components/schemas/NetworkType'
          type: array
          minItems: 1
          title: Network Types
          description: Network types to bar (or unbar). Must be non-empty.
      type: object
      required:
      - network_types
      title: LinkNetworkBarsRequest
      description: |-
        Request body for `POST /links/{id}/network-bars` and
        `POST /links/{id}/network-bars/remove`.

        The same shape covers both add and remove — symmetrical with the
        coverage operators convention (`CoverageOperatorsRequest` reused
        by add + remove).
      examples:
      - network_types:
        - 2G
        - 3G
    LinkResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal Link identifier.
        name:
          anyOf:
          - type: string
          - type: 'null'
          title: Name
          description: Customer-assigned Link name (optional).
        status:
          $ref: '#/components/schemas/LinkStatus'
          description: Lifecycle status of the Link.
        compatibility_zones:
          items:
            $ref: '#/components/schemas/CompatibilityZone'
          type: array
          title: Compatibility Zones
          description: Compatibility zones this Link can operate in (derived from the bound SIM when present).
        sim:
          anyOf:
          - $ref: '#/components/schemas/LinkSIMSummary'
          - type: 'null'
          description: The SIM bound to this Link. Null when no SIM is attached.
        network_status:
          anyOf:
          - $ref: '#/components/schemas/NetworkStatusResponse'
          - type: 'null'
          description: Last-known network connectivity for this Link. Refreshed live on the detail endpoint when the Link
            is active.
        barred_network_types:
          items:
            $ref: '#/components/schemas/NetworkType'
          type: array
          title: Barred Network Types
          description: Network types currently barred on this Link.
        locked_imei:
          anyOf:
          - type: string
          - type: 'null'
          title: Locked Imei
          description: IMEI this Link is locked to. Null when no IMEI lock is in effect.
        meta:
          additionalProperties: true
          type: object
          title: Meta
          description: Free-form customer metadata. Max 10 root-level keys, max nesting depth 3 (the root dict counts as depth
            1).
        usage:
          $ref: '#/components/schemas/LinkDataUsageSummaryResponse-Output'
          description: Aggregated data usage across the Link's activations.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the Link was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update.
      type: object
      required:
      - id
      - status
      - usage
      - created_at
      - updated_at
      title: LinkResponse
      description: |-
        A Link as visible to the caller's organisation.

        Hides internal correlation/sync fields (`provider`, `provider_id`,
        `synced_at`, `last_usage_collected_at`). The bound SIM is exposed
        as a slim `LinkSIMSummary`; full SIM detail is reachable via the
        SIM router.

        `usage` is the aggregated data-usage summary across the link's
        activations — refreshed by the background usage-collection worker,
        not on every read.
      examples:
      - barred_network_types: []
        compatibility_zones:
        - magenta
        created_at: 2026-04-01 09:30:00+00:00
        id: 9b8d4f10-2222-3333-4444-555566667777
        meta:
          site: warehouse-3
          tags:
          - pilot
        name: Berlin-IoT-001
        network_status:
          connected_rat: 4g
          country: DEU
          operator_name: Vodafone DE
          policy_status: default
        sim:
          iccid: '8949000123456789012'
          id: f47ac10b-58cc-4372-a567-0e02b2c3d479
          msisdn: '+4915112345678'
          sim_type: esim
          status: active
        status: active
        updated_at: 2026-05-26 14:22:00+00:00
        usage:
          activation_count: 3
          total_used_mib: '1234.56'
    LinkSIMSummary:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal SIM identifier.
        iccid:
          type: string
          title: Iccid
          description: ICCID — the SIM's printed serial number.
        msisdn:
          anyOf:
          - type: string
          - type: 'null'
          title: Msisdn
          description: MSISDN (phone number) assigned to the SIM, if any.
        status:
          $ref: '#/components/schemas/SIMStatus'
          description: Lifecycle status of the SIM.
        sim_type:
          $ref: '#/components/schemas/SIMType'
          description: Physical SIM or eSIM.
      type: object
      required:
      - id
      - iccid
      - status
      - sim_type
      title: LinkSIMSummary
      description: |-
        Minimal SIM info embedded on `LinkResponse`.

        Mirrors the `LinkSummary` shape on `SIMResponse` — just enough to
        identify the bound SIM without forcing a follow-up request.
      examples:
      - iccid: '8949000123456789012'
        id: f47ac10b-58cc-4372-a567-0e02b2c3d479
        msisdn: '+4915112345678'
        sim_type: esim
        status: active
    LinkSendSMSRequest:
      properties:
        message:
          type: string
          maxLength: 80
          minLength: 1
          title: Message
          description: Message body. 1-80 characters.
      type: object
      required:
      - message
      title: LinkSendSMSRequest
      description: |-
        Request body for `POST /links/{id}/sms` — send an SMS to the link's SIM.

        Length cap of 80 chars matches what the upstream system accepts in a
        single SMS payload; the service enforces the same limit and a
        longer / empty message is rejected at parse time.
      examples:
      - message: 'Heartbeat from device #42'
    LinkSortField:
      type: string
      enum:
      - name
      - status
      - created_at
      - updated_at
      title: LinkSortField
      description: |-
        Customer-visible sort fields for the link list endpoint.

        Subset of the domain sort fields — drops `synced_at` and
        `last_usage_collected_at` (internal sync timestamps).
    LinkStatus:
      type: string
      enum:
      - active
      - suspended
      - hold
      - removed
      title: LinkStatus
      description: Domain-level Link (endpoint) status.
    LinkSummary-Output:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal Link identifier.
        name:
          anyOf:
          - type: string
          - type: 'null'
          title: Name
          description: Customer-assigned Link name (optional).
        status:
          $ref: '#/components/schemas/LinkStatus'
          description: Lifecycle status of the Link.
        network_status:
          anyOf:
          - $ref: '#/components/schemas/NetworkStatusResponse'
          - type: 'null'
          description: Last-known network connectivity for this Link. Null if never observed.
      type: object
      required:
      - id
      - status
      title: LinkSummary
      description: |-
        Minimal Link info embedded on `SIMResponse`.

        Enough to identify the Link a SIM is bound to without bloating the
        SIM payload. Customers can fetch full Link detail via the Link
        router once that's built.
      examples:
      - id: 9b8d4f10-2222-3333-4444-555566667777
        name: Berlin-IoT-001
        network_status:
          connected_rat: 4g
          country: DEU
          operator_name: Vodafone DE
          policy_status: default
        status: active
    LinkUpdateRequest:
      properties:
        name:
          anyOf:
          - type: string
            maxLength: 100
            minLength: 1
          - type: 'null'
          title: Name
          description: New customer-assigned name for the Link.
        meta:
          anyOf:
          - additionalProperties: true
            type: object
          - type: 'null'
          title: Meta
          description: Replacement meta object. Pass `{}` to clear; omit to leave unchanged.
      type: object
      title: LinkUpdateRequest
      description: |-
        Request body for `PATCH /links/{id}` (partial update).

        Currently covers two local-only fields:

        - `name` — rename the Link. Whitespace-trimmed; non-null when present.
        - `meta` — replace the meta object wholesale. Must satisfy the
          same shape constraints as the domain validator (≤10 root keys,
          max depth 3). Sending `{}` clears the meta.

        Fields omitted entirely are left unchanged. Explicit `null` is
        rejected on both — `meta` cannot be unset (use `{}`), and `name`
        cannot be cleared via this endpoint.
      examples:
      - name: Berlin-IoT-001-renamed
      - meta:
          site: warehouse-3
          tags:
          - pilot
      - meta:
          site: warehouse-3
        name: Berlin-IoT-001-renamed
    NetworkStatusResponse:
      properties:
        operator_name:
          anyOf:
          - type: string
          - type: 'null'
          title: Operator Name
          description: Operator the Link is currently attached to.
        country:
          anyOf:
          - type: string
          - type: 'null'
          title: Country
          description: ISO 3166-1 alpha-3 country code of the operator.
          examples:
          - DEU
        connected_rat:
          anyOf:
          - $ref: '#/components/schemas/ConnectedRAT'
          - type: 'null'
          description: Radio access technology the Link is currently using.
        policy_status:
          anyOf:
          - $ref: '#/components/schemas/PolicyStatus'
          - type: 'null'
          description: Policy state applied to the Link by the network.
      type: object
      title: NetworkStatusResponse
      description: |-
        Last-known network connectivity status for a Link.

        Cached on the Link by the refresh worker; null while a Link has
        never been observed connected. `country` is the alpha-3 code so it
        matches the rest of the wire format.
      examples:
      - connected_rat: 4g
        country: DEU
        operator_name: Vodafone DE
        policy_status: default
    NetworkType:
      type: string
      enum:
      - 2G
      - 3G
      - 4G
      - LTE-M
      - NB-IOT
      - 5G
      title: NetworkType
      description: Supported network technology types.
    OperatorResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal operator identifier.
        name:
          type: string
          title: Name
          description: Operator display name.
          examples:
          - Vodafone DE
        tadig:
          type: string
          title: Tadig
          description: Standardised TADIG operator code.
          examples:
          - DEUD2
        country:
          type: string
          title: Country
          description: ISO 3166-1 alpha-3 country code.
          examples:
          - DEU
        compatibility_zone:
          $ref: '#/components/schemas/CompatibilityZone'
          description: Compatibility zone the operator belongs to.
        network_types:
          items:
            $ref: '#/components/schemas/NetworkType'
          type: array
          title: Network Types
          description: Network technologies supported by this operator.
      type: object
      required:
      - id
      - name
      - tadig
      - country
      - compatibility_zone
      title: OperatorResponse
      description: |-
        A telecom network operator as visible to the caller's organisation.

        `tadig` is the standardised carrier code used across providers;
        `compatibility_zone` indicates which provider ecosystem this operator
        belongs to.
      examples:
      - compatibility_zone: magenta
        country: DEU
        id: 0a1b2c3d-1111-2222-3333-444455556666
        name: Vodafone DE
        network_types:
        - 2G
        - 3G
        - 4G
        - 5G
        tadig: DEUD2
    OperatorSortField:
      type: string
      enum:
      - name
      - tadig
      - country
      - compatibility_zone
      title: OperatorSortField
      description: |-
        Customer-visible sort fields for the operator list endpoint.

        Subset of the internal `OperatorSortField` — only fields actually
        surfaced on `OperatorResponse` are sortable. Sorting by fields the
        response doesn't carry (`created_at`, `is_barred`, etc.) would be
        confusing for clients that can't see the resulting order.
    OperatorSummary:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal operator identifier.
        name:
          type: string
          title: Name
          description: Operator display name.
        tadig:
          type: string
          title: Tadig
          description: Standardised TADIG operator code.
        country:
          type: string
          title: Country
          description: ISO 3166-1 alpha-3 country code.
          examples:
          - DEU
        compatibility_zone:
          $ref: '#/components/schemas/CompatibilityZone'
          description: Compatibility zone the operator belongs to.
      type: object
      required:
      - id
      - name
      - tadig
      - country
      - compatibility_zone
      title: OperatorSummary
      description: |-
        Minimal operator info embedded in cost usage attributions.

        Just enough to display "which operator did this usage come from"
        without bloating the cost payload with full Operator objects (network
        types, bar status, country sub-fields, …). Fetch the full operator
        via the operators endpoint if needed.
      examples:
      - compatibility_zone: magenta
        country: DEU
        id: 0a1b2c3d-1111-2222-3333-444455556666
        name: Vodafone DE
        tadig: DEUD2
    OrganizationBalanceResponse:
      properties:
        organization_id:
          type: string
          format: uuid
          title: Organization Id
          description: Internal identifier of the organisation this balance belongs to.
          examples:
          - 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        amount:
          type: string
          pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          title: Amount
          description: Current main-account balance in whole currency units (EUR for all current deployments). May carry sub-cent
            precision.
          examples:
          - '1099.9548'
        currency:
          type: string
          title: Currency
          description: ISO 4217 currency code reported by the provider.
          examples:
          - EUR
        collected_at:
          type: string
          format: date-time
          title: Collected At
          description: UTC timestamp the snapshot was observed. Snapshots are append-only and skip-unchanged — gaps between
            timestamps indicate stretches where the balance didn't move.
          examples:
          - 2026-05-25 08:00:00+00:00
      type: object
      required:
      - organization_id
      - amount
      - currency
      - collected_at
      title: OrganizationBalanceResponse
      description: |-
        Latest balance snapshot for an organisation.

        Reads the most recently collected `BalanceSnapshot` from the ledger
        (populated periodically by the `collect_balances` worker, ~every 3h).
        `amount` is in whole EUR — BICS reports `maBalance` in centi-EUR and
        we normalise at the BICS boundary, so consumers see real currency
        units here.
      examples:
      - amount: '1099.9548'
        collected_at: 2026-05-25 08:00:00+00:00
        currency: EUR
        organization_id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
    OrganizationCostResponse:
      properties:
        organization_id:
          type: string
          format: uuid
          title: Organization Id
          description: Identifier of the organisation this snapshot belongs to.
        year:
          type: integer
          title: Year
          description: Year the snapshot covers.
          examples:
          - 2026
        month:
          type: integer
          maximum: 12
          minimum: 1
          title: Month
          description: Month the snapshot covers (1-12).
          examples:
          - 5
        currency:
          anyOf:
          - type: string
          - type: 'null'
          title: Currency
          description: ISO 4217 currency code for all amounts. Typically `EUR`.
          examples:
          - EUR
        sim_activation_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Sim Activation Fee
        sim_monthly_active_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Sim Monthly Active Fee
        sim_monthly_active_feature_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Sim Monthly Active Feature Fee
        oem_sim_activation_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Oem Sim Activation Fee
        apn_setup_monthly_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Apn Setup Monthly Fee
        vpn_setup_monthly_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Vpn Setup Monthly Fee
        vpn_setup_one_time_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Vpn Setup One Time Fee
        apn_setup_one_time_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Apn Setup One Time Fee
        local_sim_profile_download_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Local Sim Profile Download Fee
        local_sim_hosting_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Local Sim Hosting Fee
        local_sim_management_fee:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Local Sim Management Fee
        rental_amount:
          anyOf:
          - type: string
            pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
          - type: 'null'
          title: Rental Amount
        usage_lines:
          items:
            $ref: '#/components/schemas/CostUsageLineResponse-Output'
          type: array
          title: Usage Lines
          description: Per-service usage breakdown with operator attribution.
        collected_at:
          type: string
          format: date-time
          title: Collected At
          description: UTC timestamp the snapshot was observed at the provider.
      type: object
      required:
      - organization_id
      - year
      - month
      - collected_at
      title: OrganizationCostResponse
      description: |-
        Latest monthly cost snapshot for an organisation.

        Snapshots are append-only — the collector runs every 3h and may
        write multiple snapshots per `(year, month)` as the month progresses.
        This response is always the most recent one collected for the given
        `(year, month)`.

        Flat fees are org-wide totals; per-operator attribution lives in
        `usage_lines`.
      examples:
      - collected_at: 2026-05-25 09:00:00+00:00
        currency: EUR
        month: 5
        organization_id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        sim_activation_fee: '10.00'
        sim_monthly_active_fee: '120.00'
        usage_lines:
        - amount: '12.34'
          operators:
          - compatibility_zone: magenta
            country: DEU
            id: 0a1b2c3d-1111-2222-3333-444455556666
            name: Vodafone DE
            tadig: DEUD2
          service_name: DATA
          usage: '1024.50'
        year: 2026
    PagedResponse_ActivationResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ActivationResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[ActivationResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_BulkTaskItemResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/BulkTaskItemResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[BulkTaskItemResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_BulkTaskResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/BulkTaskResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[BulkTaskResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_CoverageResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/CoverageResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[CoverageResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_LinkResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/LinkResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[LinkResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_OperatorResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/OperatorResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[OperatorResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_PlanResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/PlanResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[PlanResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_SIMResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/SIMResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[SIMResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PagedResponse_SentSMSResponse_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/SentSMSResponse'
          type: array
          title: Items
          description: Items on the current page.
        total:
          type: integer
          title: Total
          description: Total number of items across all pages.
          examples:
          - 42
        page:
          type: integer
          title: Page
          description: Current page number (1-based).
          examples:
          - 1
        page_size:
          type: integer
          title: Page Size
          description: Items per page used to compute this response.
          examples:
          - 25
        pages:
          type: integer
          title: Pages
          description: Total number of pages at the current `page_size`.
          examples:
          - 2
      type: object
      required:
      - items
      - total
      - page
      - page_size
      - pages
      title: PagedResponse[SentSMSResponse]
      examples:
      - items: []
        page: 1
        page_size: 25
        pages: 1
        total: 0
    PlanCreateRequest:
      properties:
        name:
          type: string
          minLength: 1
          title: Name
          description: Display name for the plan. Not required to be unique.
        description:
          anyOf:
          - type: string
          - type: 'null'
          title: Description
          description: Free-text description.
        coverage_id:
          type: string
          format: uuid
          title: Coverage Id
          description: Coverage this plan applies to.
        included_data_mb:
          type: integer
          minimum: 0
          title: Included Data Mb
          description: Bundled data in MB.
        validity_days:
          type: integer
          minimum: 1
          title: Validity Days
          description: Plan validity from activation, in days.
        activation_type:
          $ref: '#/components/schemas/ActivationType'
        base_throttle:
          $ref: '#/components/schemas/ThrottleSpeed'
          description: Max speed the plan can ever provide. Use `unlimited` for no throttle — `null` is not accepted (use
            the explicit `unlimited` value instead, so the field always has a meaningful answer).
        data_cap:
          anyOf:
          - $ref: '#/components/schemas/DataCapSchema'
          - type: 'null'
          description: In-plan soft cap — speed action applied at threshold.
        notifications:
          items:
            $ref: '#/components/schemas/PlanNotificationCreateRequest'
          type: array
          title: Notifications
          description: Initial set of plan notifications. At most one per `type`.
        reactivation_trigger:
          anyOf:
          - $ref: '#/components/schemas/ReactivationTrigger'
          - type: 'null'
          description: What triggers plan renewal. Presence is immutable post-create.
        max_reactivations:
          anyOf:
          - type: integer
          - type: 'null'
          title: Max Reactivations
          description: Cap on automatic reactivations. `-1` means unlimited.
      type: object
      required:
      - name
      - coverage_id
      - included_data_mb
      - validity_days
      - activation_type
      - base_throttle
      title: PlanCreateRequest
      description: |-
        Request body for `POST /plans`.

        Coverage must already exist in the caller's catalogue; unknown
        `coverage_id` returns 404. Names are not constrained to be unique
        — pick any label that helps you identify the plan in your UI.

        `reactivation_trigger` presence is immutable post-create: a plan
        created without a trigger cannot have one added later, and vice
        versa. The trigger *type* (between two non-null values) can change
        via `PATCH` once that endpoint lands.
      examples:
      - activation_type: immediate
        base_throttle: 10mbps
        coverage_id: f47ac10b-58cc-4372-a567-0e02b2c3d479
        data_cap:
          action: throttle_256kbps
          period: monthly
          period_multiplier: 1
          threshold_mb: 1024
        description: 1GB / 30d, throttle to 256kbps on cap.
        included_data_mb: 1024
        max_reactivations: 12
        name: Basic 1GB Monthly
        notifications:
        - message: You've used 80% of your data.
          threshold: 80
          type: data_consumption_relative
        reactivation_trigger: on_expiration
        validity_days: 30
    PlanNotificationCreateRequest:
      properties:
        type:
          $ref: '#/components/schemas/PlanNotificationType'
          description: Notification trigger type.
        message:
          type: string
          maxLength: 85
          minLength: 1
          title: Message
          description: SMS body sent to the link (max 85 chars).
        threshold:
          anyOf:
          - type: integer
          - type: 'null'
          title: Threshold
          description: Threshold value — MB for `data_consumption_absolute`, percentage 1-100 for `data_consumption_relative`,
            null for activation/expiration.
      type: object
      required:
      - type
      - message
      title: PlanNotificationCreateRequest
      description: |-
        Create-side shape for a plan notification.

        Reused inline in `PlanCreateRequest.notifications` and as the body
        of `POST /plans/{plan_id}/notifications`. Only one notification per
        `type` per plan is allowed — duplicates return 409.

        `threshold` is required for the data-consumption types and forbidden
        for the lifecycle types; the upstream system enforces the rule, so
        a violation surfaces as a 502 with `upstream_error` rather than 422.
      examples:
      - message: You've used 80% of your data this month.
        threshold: 80
        type: data_consumption_relative
    PlanNotificationResponse:
      properties:
        id:
          type: integer
          title: Id
          description: Notification identifier (assigned on create).
        type:
          $ref: '#/components/schemas/PlanNotificationType'
          description: Notification trigger type.
        message:
          type: string
          title: Message
          description: SMS body sent to the link.
        threshold:
          anyOf:
          - type: integer
          - type: 'null'
          title: Threshold
          description: Threshold value — MB for `data_consumption_absolute`, percentage for `data_consumption_relative`, null
            for activation/expiration.
      type: object
      required:
      - id
      - type
      - message
      title: PlanNotificationResponse
      description: |-
        A plan notification as it appears on `PlanResponse.notifications`.

        `threshold` semantics depend on `type`: MB for
        `data_consumption_absolute`, percentage (1-100) for
        `data_consumption_relative`, null for the lifecycle types
        (`plan_activation`, `plan_expiration`).
      examples:
      - id: 42
        message: You've used 80% of your data this month.
        threshold: 80
        type: data_consumption_relative
    PlanNotificationType:
      type: string
      enum:
      - plan_activation
      - data_consumption_absolute
      - data_consumption_relative
      - plan_expiration
      title: PlanNotificationType
      description: Types of plan notifications sent via SMS to the link.
    PlanNotificationUpdateRequest:
      properties:
        message:
          type: string
          maxLength: 85
          minLength: 1
          title: Message
          description: New SMS body (max 85 chars).
        threshold:
          anyOf:
          - type: integer
          - type: 'null'
          title: Threshold
          description: New threshold (semantics depend on the notification's type).
      type: object
      required:
      - message
      title: PlanNotificationUpdateRequest
      description: |-
        Replace the `message` and `threshold` of an existing notification.

        `type` is fixed at creation and cannot be changed — to change the
        type, delete and re-add. `message` is required because the upstream
        system implements notification update as a remove + re-add
        operation atomically. `threshold` rules follow the type (see
        `PlanNotificationResponse.threshold`):

        - lifecycle types (`plan_activation`, `plan_expiration`): must be
          omitted or `null`
        - `data_consumption_absolute`: required, positive integer (MB)
        - `data_consumption_relative`: required, percentage in [50, 100]

        Type-mismatched thresholds are rejected as 422 `validation_error`
        by the service.
      examples:
      - message: You've used 95% of your data.
        threshold: 95
    PlanResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal plan identifier.
        name:
          type: string
          title: Name
          description: Customer-assigned plan name.
        description:
          anyOf:
          - type: string
          - type: 'null'
          title: Description
          description: Free-text description.
        coverage:
          $ref: '#/components/schemas/CoverageSummary'
          description: Coverage this plan applies to.
        included_data_mb:
          type: integer
          title: Included Data Mb
          description: Bundled data in MB.
        validity_days:
          type: integer
          title: Validity Days
          description: Plan validity from activation, in days.
        base_throttle:
          $ref: '#/components/schemas/ThrottleSpeed'
          description: Base throttle speed. `unlimited` means no throttle. Always present — historical rows with no throttle
            set surface as `unlimited` rather than `null`.
        data_cap:
          anyOf:
          - $ref: '#/components/schemas/DataCapSchema'
          - type: 'null'
          description: In-plan soft cap.
        activation_type:
          $ref: '#/components/schemas/ActivationType'
          description: When the plan activates after assignment.
        reactivation_trigger:
          anyOf:
          - $ref: '#/components/schemas/ReactivationTrigger'
          - type: 'null'
          description: Renewal trigger, if any.
        max_reactivations:
          anyOf:
          - type: integer
          - type: 'null'
          title: Max Reactivations
          description: Max reactivations (`-1` = unlimited).
        notifications:
          items:
            $ref: '#/components/schemas/PlanNotificationResponse'
          type: array
          title: Notifications
          description: SMS notifications configured for this plan.
        compatibility_zones:
          items:
            $ref: '#/components/schemas/CompatibilityZone'
          type: array
          title: Compatibility Zones
          description: Compatibility zones derived from the coverage.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the plan was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update.
      type: object
      required:
      - id
      - name
      - coverage
      - included_data_mb
      - validity_days
      - base_throttle
      - activation_type
      - created_at
      - updated_at
      title: PlanResponse
      description: |-
        A plan as visible to the caller's organisation.

        Embeds a slim `CoverageSummary` so the caller can identify the
        plan's coverage without an extra request. Notifications are
        embedded read-only — managed via the
        `/plans/{plan_id}/notifications` sub-resource. Internal fields
        (`provider_id`, `synced_at`, lifecycle `status`) are hidden.
      examples:
      - activation_type: immediate
        base_throttle: 10mbps
        compatibility_zones:
        - magenta
        coverage:
          compatibility_zones:
          - magenta
          id: f47ac10b-58cc-4372-a567-0e02b2c3d479
          name: EU + UK
        created_at: 2026-04-01 09:30:00+00:00
        data_cap:
          action: throttle_256kbps
          period: monthly
          period_multiplier: 1
          threshold_mb: 1024
        description: 1GB / 30d, throttle to 256kbps on cap.
        id: 2a3b4c5d-1111-2222-3333-444455556666
        included_data_mb: 1024
        max_reactivations: 12
        name: Basic 1GB Monthly
        notifications:
        - id: 42
          message: You've used 80% of your data.
          threshold: 80
          type: data_consumption_relative
        reactivation_trigger: on_expiration
        updated_at: 2026-05-20 14:22:00+00:00
        validity_days: 30
    PlanSortField:
      type: string
      enum:
      - name
      - created_at
      - updated_at
      - validity_days
      - included_data_mb
      - base_throttle
      title: PlanSortField
      description: |-
        Customer-visible sort fields for the plan list endpoint.

        Subset of the domain sort fields. `status` is excluded (binary,
        hidden from responses). `coverage_id` is excluded (UUID, not user-
        meaningful for ordering — customers filter by coverage instead).
    PlanUpdateRequest:
      properties:
        name:
          anyOf:
          - type: string
            minLength: 1
          - type: 'null'
          title: Name
          description: New plan name.
        description:
          anyOf:
          - type: string
          - type: 'null'
          title: Description
          description: New description; pass `null` to clear.
        coverage_id:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          title: Coverage Id
          description: New coverage id.
        included_data_mb:
          anyOf:
          - type: integer
            minimum: 0
          - type: 'null'
          title: Included Data Mb
          description: New bundled data in MB.
        validity_days:
          anyOf:
          - type: integer
            minimum: 1
          - type: 'null'
          title: Validity Days
          description: New validity in days.
        base_throttle:
          anyOf:
          - $ref: '#/components/schemas/ThrottleSpeed'
          - type: 'null'
          description: New base throttle. Use `unlimited` for no throttle. Omit the field to leave the current value unchanged
            — `null` here would canonicalize to `unlimited` on read, so use the explicit value instead to keep intent obvious.
        data_cap:
          anyOf:
          - $ref: '#/components/schemas/DataCapSchema'
          - type: 'null'
          description: New data cap; pass `null` to clear.
        activation_type:
          anyOf:
          - $ref: '#/components/schemas/ActivationType'
          - type: 'null'
          description: New activation type.
        reactivation_trigger:
          anyOf:
          - $ref: '#/components/schemas/ReactivationTrigger'
          - type: 'null'
          description: New reactivation trigger type. Presence cannot be toggled — to change between two non-null values,
            send the new value.
        max_reactivations:
          anyOf:
          - type: integer
          - type: 'null'
          title: Max Reactivations
          description: New max reactivations (`-1` = unlimited); pass `null` to clear.
      type: object
      title: PlanUpdateRequest
      description: |-
        Request body for `PATCH /plans/{plan_id}`.

        Patch-style — only fields **explicitly present** in the payload are
        forwarded. Omitting a field leaves it unchanged; sending `null` for
        a nullable field clears it. (The router distinguishes the two via
        `model_fields_set`; `null` is rejected for non-nullable fields by
        schema typing.)

        Constraints inherited from the service:
        - `reactivation_trigger` presence is immutable: cannot be added to
          a plan that lacks one, nor removed from one that has it. Changing
          between two non-null values is allowed.
        - `data_cap.threshold_mb` cannot exceed the plan's effective
          `included_data_mb` (current value if `included_data_mb` isn't
          being changed, new value if it is). Caught at the service as a
          422 `validation_error`.
        - `base_throttle` does not accept `null` here either — use the
          explicit `unlimited` value (matches the create endpoint).

        `name` changes are local-only; the upstream system stores a
        synthesized id and is never told about renames.
      examples:
      - name: Basic 1GB Monthly (v2)
        validity_days: 60
      - max_reactivations: -1
    PolicyStatus:
      type: string
      enum:
      - default
      - blocked
      - throttle
      title: PolicyStatus
      description: Policy status for a connected link.
    ReactivationTrigger:
      type: string
      enum:
      - on_expiration
      - on_expiration_or_exhaustion
      title: ReactivationTrigger
      description: What triggers plan renewal.
    SIMBulkProvisionRequest:
      properties:
        name_prefix:
          type: string
          maxLength: 50
          minLength: 1
          title: Name Prefix
          description: Prefix for each provisioned link's name; the per-item suffix is `_{counter}`.
        sim_ids:
          items:
            type: string
            format: uuid
          type: array
          maxItems: 5000
          minItems: 1
          title: Sim Ids
          description: SIMs to provision. Must be non-empty; duplicates are rejected.
        plan_ids:
          anyOf:
          - items:
              type: string
              format: uuid
            type: array
          - type: 'null'
          title: Plan Ids
          description: Plans to activate on every new link. Omit to leave each link pre-active.
        counter_start:
          type: integer
          minimum: 0
          title: Counter Start
          description: First value used for the `_{counter}` suffix; subsequent items get `+1` each.
          default: 1
      type: object
      required:
      - name_prefix
      - sim_ids
      title: SIMBulkProvisionRequest
      description: |-
        Request body for `POST /sims/bulk-provision`.

        Provisions a Link for each SIM in `sim_ids`. The Link is named
        `{name_prefix}_{counter}` where `counter` starts at `counter_start`
        (default 1) and advances per item — so a call with `name_prefix="DC1"`,
        three SIMs, and `counter_start=10` produces links named `DC1_10`,
        `DC1_11`, `DC1_12`. If `plan_ids` is supplied, those plans are
        activated on each new link at creation; otherwise the link stays
        pre-active.

        Duplicate ids in `sim_ids` are rejected at parse time (422) — silent
        deduplication would mask user input bugs that double-provision the
        same SIM with different names.
      examples:
      - counter_start: 1
        name_prefix: Berlin-IoT
        plan_ids:
        - b14d2a91-1111-2222-3333-444455556666
        sim_ids:
        - f47ac10b-58cc-4372-a567-0e02b2c3d479
        - d3a1a9b4-2222-3333-4444-555566667777
    SIMInstallationResponse:
      properties:
        activation_code:
          anyOf:
          - type: string
          - type: 'null'
          title: Activation Code
          description: Raw LPA activation string for the eSIM. Null for physical SIMs.
        android_link:
          anyOf:
          - type: string
          - type: 'null'
          title: Android Link
          description: Android deeplink that opens the system eSIM installer prefilled with this code.
        ios_link:
          anyOf:
          - type: string
          - type: 'null'
          title: Ios Link
          description: iOS deeplink that opens the system eSIM installer prefilled with this code.
        qr_svg:
          anyOf:
          - type: string
          - type: 'null'
          title: Qr Svg
          description: Inline SVG markup of the LPA QR code, ready to embed in HTML.
      type: object
      title: SIMInstallationResponse
      description: |-
        eSIM installation view for a SIM.

        All fields are derived from the SIM's `activation_code`. Physical
        SIMs (no activation code) return all-null — that's a normal state,
        not an error.
      examples:
      - activation_code: LPA:1$smdp.example.com$ABCDEF...
        android_link: https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$smdp.example.com$ABCDEF...
        ios_link: https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$smdp.example.com$ABCDEF...
        qr_svg: <svg xmlns=...>...</svg>
    SIMResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal SIM identifier.
        iccid:
          type: string
          title: Iccid
          description: ICCID — the SIM's printed serial number.
        msisdn:
          anyOf:
          - type: string
          - type: 'null'
          title: Msisdn
          description: MSISDN (phone number) assigned to the SIM, if any.
        imsi:
          anyOf:
          - type: string
          - type: 'null'
          title: Imsi
          description: IMSI on the SIM profile.
        imei:
          anyOf:
          - type: string
          - type: 'null'
          title: Imei
          description: IMEI of the device the SIM was last seen in, if known.
        eid:
          anyOf:
          - type: string
          - type: 'null'
          title: Eid
          description: eUICC identifier — only present for eSIMs.
        status:
          $ref: '#/components/schemas/SIMStatus'
          description: Lifecycle status of the SIM.
        sim_type:
          $ref: '#/components/schemas/SIMType'
          description: Physical SIM or eSIM.
        is_attached:
          type: boolean
          title: Is Attached
          description: 'Whether the upstream system reports the SIM as currently attached. Distinct from `link`: this reflects
            upstream-reported attach state, while `link` reflects our local binding.'
        compatibility_zones:
          items:
            $ref: '#/components/schemas/CompatibilityZone'
          type: array
          title: Compatibility Zones
          description: Compatibility zones this SIM can operate in.
        esim_profile_status:
          anyOf:
          - $ref: '#/components/schemas/ESimProfileStatus'
          - type: 'null'
          description: eSIM profile state. Null for physical SIMs.
        link:
          anyOf:
          - $ref: '#/components/schemas/LinkSummary-Output'
          - type: 'null'
          description: The Link this SIM is bound to in our system. Null if not bound.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: UTC timestamp when the SIM was first synced into our system.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: UTC timestamp of the last update from sync.
      type: object
      required:
      - id
      - iccid
      - status
      - sim_type
      - is_attached
      - created_at
      - updated_at
      title: SIMResponse
      description: |-
        A SIM as visible to the caller's organisation.

        Hides internal correlation/sync fields (`provider_id`, `synced_at`)
        and the raw `activation_code` (the install view surfaces it
        properly). `is_attached` is the provider-reported attach state;
        `link` is the local DB view of the SIM's bound Link — these can
        diverge briefly (sync lag), which is why both are exposed.
      examples:
      - compatibility_zones:
        - magenta
        created_at: 2026-04-01 09:30:00+00:00
        eid: '89049032123456789012345678901234'
        esim_profile_status: installed
        iccid: '8949000123456789012'
        id: f47ac10b-58cc-4372-a567-0e02b2c3d479
        imei: '356938035643809'
        imsi: '262011234567890'
        is_attached: true
        link:
          id: 9b8d4f10-2222-3333-4444-555566667777
          name: Berlin-IoT-001
          network_status:
            connected_rat: 4g
            country: DEU
            operator_name: Vodafone DE
            policy_status: default
          status: active
        msisdn: '+4915112345678'
        sim_type: esim
        status: active
        updated_at: 2026-05-26 14:22:00+00:00
    SIMSortField:
      type: string
      enum:
      - iccid
      - status
      - sim_type
      - created_at
      - updated_at
      title: SIMSortField
      description: |-
        Customer-visible sort fields for the SIM list endpoint.

        Subset of the domain sort fields — covers identifiers + lifecycle
        timestamps, both useful to customers. `synced_at` is hidden
        (internal sync timestamp).
    SIMStatus:
      type: string
      enum:
      - available
      - active
      - suspended
      - inventory
      title: SIMStatus
      description: Domain-level SIM status.
    SIMType:
      type: string
      enum:
      - physical
      - esim
      title: SIMType
      description: SIM form factor type.
    SentSMSResponse:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Internal identifier for this send attempt.
        message:
          type: string
          title: Message
          description: Message body that was submitted.
        status:
          $ref: '#/components/schemas/SentSMSStatus'
          description: Outcome of the send attempt.
        failure_reason:
          anyOf:
          - type: string
          - type: 'null'
          title: Failure Reason
          description: Reason for the failure when `status` is `failed`. Null when `status` is `sent`.
        sent_at:
          type: string
          format: date-time
          title: Sent At
          description: UTC timestamp when the attempt was made.
      type: object
      required:
      - id
      - message
      - status
      - sent_at
      title: SentSMSResponse
      description: |-
        A single SMS send attempt on a link.

        One row per attempt — successes and failures both surface here.
        `failure_reason` is populated only when `status` is `failed`.
      examples:
      - id: 3b1c0d50-0001-2222-3333-444455556666
        message: 'Heartbeat from device #42'
        sent_at: 2026-06-09 08:15:00+00:00
        status: sent
      - failure_reason: Upstream rejected the message.
        id: 3b1c0d50-0001-2222-3333-444455557777
        message: 'Heartbeat from device #42'
        sent_at: 2026-06-09 08:14:00+00:00
        status: failed
    SentSMSSortField:
      type: string
      enum:
      - sent_at
      - status
      title: SentSMSSortField
      description: |-
        Customer-visible sort fields for the SMS history endpoint.

        Mirrors the domain enum 1:1; included here so the wire surface
        stays decoupled from the domain layer.
    SentSMSStatus:
      type: string
      enum:
      - sent
      - failed
      title: SentSMSStatus
      description: Outcome of an SMS send attempt.
    SortOrder:
      type: string
      enum:
      - asc
      - desc
      title: SortOrder
    ThrottleSpeed:
      type: string
      enum:
      - unlimited
      - 128kbps
      - 256kbps
      - 384kbps
      - 512kbps
      - 1250kbps
      - 1mbps
      - 2mbps
      - 5mbps
      - 10mbps
      - 20mbps
      title: ThrottleSpeed
      description: Baseline throttle speed — max speed the plan can ever provide.
security:
- bearerAuth: []
tags:
- name: Auth
  description: |-
    OAuth 2.0 authentication using the **Client Credentials** grant type ([RFC 6749 §4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)).

    Use your **Client ID** and **Client Secret** from [OmaxTelecom Console](https://console.omaxtelecom.com) to obtain an access token.
- name: Coverages
  description: Coverage zone management — define which mobile operators are available for your plans. Coverages group operators
    by country and compatibility zone.
- name: SIMs
  description: SIM card inventory — list and view SIMs, bulk-provision links, retrieve eSIM installation details, and refresh
    SIM data from upstream systems.
- name: Plans
  description: Data plan management — create and configure plans with coverage, data allowances, throttle speeds, validity,
    and usage notifications.
- name: Links
  description: Link lifecycle management — create and manage service endpoints bound to SIMs. Includes activations, suspend/resume,
    SMS, network bars, IMEI lock, and bulk operations.
- name: Tasks
  description: Bulk task tracking — monitor asynchronous bulk operations initiated via `POST /links/bulk-*` or `POST /sims/bulk-provision`.
- name: Organization
  description: Organisation profile and account data — view your tenant info, latest balance snapshot, and monthly cost breakdown.
paths:
  /auth/token:
    servers:
      - url: https://api.omaxtelecom.com
    post:
      tags:
      - Auth
      summary: Get Access Token
      description: |-
        ## Get Access Token

        Obtain a Bearer access token using the OAuth 2.0 **Client Credentials** grant ([RFC 6749 §4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)).

        ### Setup

        1. Get your `client_id` from [OmaxTelecom Console](https://console.omaxtelecom.com)
        2. Get your `client_secret` from [OmaxTelecom Console](https://console.omaxtelecom.com)

        ### Request

        ```
        POST https://api.omaxtelecom.com/auth/token
        Content-Type: application/x-www-form-urlencoded

        grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}
        ```

        ### Response (200 OK)

        ```json
        {
          "access_token": "eyJhbGciOiJSUzI1NiIs...",
          "expires_in": 300,
          "token_type": "Bearer",
          "scope": "openid"
        }
        ```

        ### Token Usage

        Include the token in the `Authorization` header:

        ```
        Authorization: Bearer {access_token}
        ```
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              properties:
                grant_type:
                  type: string
                  description: OAuth 2.0 grant type (RFC 6749 §4.4)
                  example: client_credentials
                client_id:
                  type: string
                  description: Your Client ID
                  example: '{{client_id}}'
                client_secret:
                  type: string
                  description: Your Client Secret
                  example: '{{client_secret}}'
      security:
      - noauthAuth: []
      parameters:
      - name: Content-Type
        in: header
        schema:
          type: string
        example: application/x-www-form-urlencoded
      - name: Accept
        in: header
        schema:
          type: string
        example: application/json
      responses:
        '200':
          description: Successful response
          content:
            application/json: {}
  /coverages:
    servers: &id001
    - url: https://api.omaxtelecom.com/telkor
      description: Production
    get:
      tags:
      - Coverages
      summary: List coverages
      description: |-
        Paginated list of coverages in your organisation.

        Filters combine with AND semantics; repeatable filters
        (`operator_ids`, `countries`, `compatibility_zones`) match any of
        the provided values. `search` does a case-insensitive substring
        match across the coverage name, id, and any included operator
        name. Soft-deleted coverages are not returned.
      operationId: list_coverages_coverages_get
      security:
      - bearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/CoverageSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently updated first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently updated first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: operator_ids
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              type: string
              format: uuid
          - type: 'null'
          description: Filter to coverages that include any of these operator ids. Repeatable.
          title: Operator Ids
        description: Filter to coverages that include any of these operator ids. Repeatable.
      - name: countries
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              type: string
          - type: 'null'
          description: Filter by ISO 3166-1 alpha-3 country codes of included operators. Repeatable. Unknown codes return
            422.
          title: Countries
        description: Filter by ISO 3166-1 alpha-3 country codes of included operators. Repeatable. Unknown codes return 422.
      - name: compatibility_zones
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              $ref: '#/components/schemas/CompatibilityZone'
          - type: 'null'
          description: Filter by compatibility zone of included operators. Repeatable.
          title: Compatibility Zones
        description: Filter by compatibility zone of included operators. Repeatable.
      - name: search
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Free-text match against coverage name, id, or included operator name.
          title: Search
        description: Free-text match against coverage name, id, or included operator name.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_CoverageResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    post:
      tags:
      - Coverages
      summary: Create a coverage
      description: |-
        Create a coverage from a non-empty set of existing operators.

        All operators must belong to the same compatibility-zone family;
        mixing zones across providers is rejected with 422. The supplied
        `name` is stored locally and never sent to the provider — the
        provider receives a synthesized id.
      operationId: create_coverage_coverages_post
      security:
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CoverageCreateRequest'
      responses:
        '201':
          description: Coverage created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: One or more operator ids do not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /coverages/operators:
    servers: *id001
    get:
      tags:
      - Coverages
      summary: List operators usable in coverages
      description: |-
        Paginated list of operators in your organisation's catalogue.

        Operators are the building blocks of coverages — pick a set to
        create or extend a coverage. Filters combine with AND semantics;
        repeatable filters (`countries`, `compatibility_zones`,
        `network_types`) match any of the provided values. `search` does a
        case-insensitive substring match across name, TADIG, and id.
      operationId: list_operators_coverages_operators_get
      security:
      - bearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/OperatorSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently updated first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently updated first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: countries
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              type: string
          - type: 'null'
          description: Filter by ISO 3166-1 alpha-3 country codes. Repeatable. Unknown codes return 422.
          title: Countries
        description: Filter by ISO 3166-1 alpha-3 country codes. Repeatable. Unknown codes return 422.
      - name: compatibility_zones
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              $ref: '#/components/schemas/CompatibilityZone'
          - type: 'null'
          description: Filter by compatibility zone. Repeatable.
          title: Compatibility Zones
        description: Filter by compatibility zone. Repeatable.
      - name: network_types
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              $ref: '#/components/schemas/NetworkType'
          - type: 'null'
          description: Filter by supported network technology. Repeatable.
          title: Network Types
        description: Filter by supported network technology. Repeatable.
      - name: search
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Free-text match against name, TADIG, or id.
          title: Search
        description: Free-text match against name, TADIG, or id.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_OperatorResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /coverages/{coverage_id}:
    servers: *id001
    get:
      tags:
      - Coverages
      summary: Get a coverage
      description: Fetch a single coverage by id within the caller's organisation.
      operationId: get_coverage_coverages__coverage_id__get
      security:
      - bearerAuth: []
      parameters:
      - name: coverage_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Coverage Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Coverage not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Coverage has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    patch:
      tags:
      - Coverages
      summary: Rename a coverage
      description: |-
        Rename a coverage.

        Local-only change — the provider's synthesized id is unaffected and
        no provider call is made. Returns the updated coverage.
      operationId: rename_coverage_coverages__coverage_id__patch
      security:
      - bearerAuth: []
      parameters:
      - name: coverage_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Coverage Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CoverageRenameRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Coverage not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Coverage has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    delete:
      tags:
      - Coverages
      summary: Delete a coverage
      description: |-
        Soft-delete a coverage.

        The provider's resource is deleted and the coverage is marked
        `REMOVED` locally — subsequent reads return 410. The coverage's
        operators are unaffected (they remain in the org's catalogue). A
        coverage that is still used by any plan cannot be deleted (422).

        Returns **204 No Content** — REST-standard for delete. Other
        modifying endpoints return the updated entity, but a returned body
        here would be identical to a pre-delete GET (we deliberately don't
        expose `status` on the wire), so it would signal nothing the status
        code didn't already.
      operationId: delete_coverage_coverages__coverage_id__delete
      security:
      - bearerAuth: []
      parameters:
      - name: coverage_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Coverage Id
      responses:
        '204':
          description: Coverage deleted.
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Coverage not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Coverage has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /coverages/{coverage_id}/operators:
    servers: *id001
    post:
      tags:
      - Coverages
      summary: Add operators to a coverage
      description: |-
        Add operators to an existing coverage.

        Idempotent — ids already present in the coverage are silently
        skipped. All ids must resolve to operators in the same
        compatibility-zone family as the coverage's existing operators;
        mixing families across providers is rejected with 422. Returns the
        updated coverage.
      operationId: add_operators_to_coverage_coverages__coverage_id__operators_post
      security:
      - bearerAuth: []
      parameters:
      - name: coverage_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Coverage Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CoverageOperatorsRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Coverage or one of the operator ids does not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Coverage has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /coverages/{coverage_id}/operators/remove:
    servers: *id001
    post:
      tags:
      - Coverages
      summary: Remove operators from a coverage
      description: |-
        Remove operators from an existing coverage.

        Bulk action endpoint — uses POST with a `/remove` action segment
        rather than DELETE-with-body, which the HTTP spec discourages and
        some proxies strip. The operators themselves are not destroyed;
        they remain in the org's catalogue and can be added to other
        coverages.

        Idempotent — ids not currently in the coverage are silently
        skipped. Removing all operators (leaving the coverage with zero)
        is rejected with 422. Returns the updated coverage.
      operationId: remove_operators_from_coverage_coverages__coverage_id__operators_remove_post
      security:
      - bearerAuth: []
      parameters:
      - name: coverage_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Coverage Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CoverageOperatorsRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Coverage or one of the operator ids does not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Coverage has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /sims:
    servers: *id001
    get:
      tags:
      - SIMs
      summary: List SIMs
      description: |-
        Paginated list of SIMs in your organisation.

        Filters combine with AND semantics. `compatibility_zones` is
        repeatable (OR within the filter). `search` does a case-insensitive
        substring match across identifier fields. `for_plan_id` is a
        convenience filter — resolves the plan's compatibility zones and
        AND's them with whatever else is set; if `compatibility_zones` is
        also provided, the plan-derived set takes precedence (the service
        overwrites the kwarg).
      operationId: list_sims_sims_get
      security:
      - bearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/SIMSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently updated first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently updated first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/SIMStatus'
          - type: 'null'
          description: Filter by SIM lifecycle status.
          title: Status
        description: Filter by SIM lifecycle status.
      - name: sim_type
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/SIMType'
          - type: 'null'
          description: Filter by SIM form factor (physical or eSIM).
          title: Sim Type
        description: Filter by SIM form factor (physical or eSIM).
      - name: compatibility_zones
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              $ref: '#/components/schemas/CompatibilityZone'
          - type: 'null'
          description: Filter by compatibility zone. Repeatable.
          title: Compatibility Zones
        description: Filter by compatibility zone. Repeatable.
      - name: search
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Free-text match against id, ICCID, MSISDN, IMSI, IMEI, or EID.
          title: Search
        description: Free-text match against id, ICCID, MSISDN, IMSI, IMEI, or EID.
      - name: for_plan_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          description: Return only SIMs compatible with the given plan. Resolves the plan's compatibility zones and applies
            them as a filter.
          title: For Plan Id
        description: Return only SIMs compatible with the given plan. Resolves the plan's compatibility zones and applies
          them as a filter.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_SIMResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /sims/bulk-provision:
    servers: *id001
    post:
      tags:
      - SIMs
      summary: Provision links for multiple SIMs in bulk
      description: |-
        Provision a link for each SIM in the request, asynchronously.

        Each item generates a Link named `{name_prefix}_{counter}`, optionally
        activating the same `plan_ids` on it. The work runs in the background:
        this endpoint returns **202** with a `BulkTaskResponse` immediately;
        customers poll `GET /tasks/{task_id}` to track per-item progress and
        `GET /tasks/{task_id}/items` for per-SIM outcomes (success / failure
        reason).

        Per-item failures (e.g. a SIM already linked) don't fail the whole
        task — they're recorded against the offending item only and the task
        keeps processing the rest.
      operationId: bulk_provision_sims_sims_bulk_provision_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SIMBulkProvisionRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /sims/{sim_id}:
    servers: *id001
    get:
      tags:
      - SIMs
      summary: Get a SIM
      description: Fetch a single SIM by id within the caller's organisation.
      operationId: get_sim_sims__sim_id__get
      security:
      - bearerAuth: []
      parameters:
      - name: sim_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Sim Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIMResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: SIM not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /sims/{sim_id}/installation:
    servers: *id001
    get:
      tags:
      - SIMs
      summary: Get the eSIM installation view for a SIM
      description: |-
        Build the eSIM installation view for a SIM.

        Returns the raw LPA `activation_code`, an inline QR SVG, and
        Android / iOS deeplinks that prefill the system eSIM installer.
        Physical SIMs (no activation code) return all-null — not an error,
        just nothing to install.
      operationId: get_sim_installation_sims__sim_id__installation_get
      security:
      - bearerAuth: []
      parameters:
      - name: sim_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Sim Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIMInstallationResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: SIM not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /sims/{sim_id}/refresh:
    servers: *id001
    post:
      tags:
      - SIMs
      summary: Refresh a SIM from its upstream system
      description: |-
        Pull fresh state for a single SIM from upstream and return it.

        Background workers periodically sync all SIMs; this endpoint lets
        customers force-refresh one on demand. If upstream returns no data,
        the existing locally-stored SIM is returned unchanged — same
        semantics as the worker path.
      operationId: refresh_sim_sims__sim_id__refresh_post
      security:
      - bearerAuth: []
      parameters:
      - name: sim_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Sim Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIMResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: SIM not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /plans:
    servers: *id001
    get:
      tags:
      - Plans
      summary: List plans
      description: |-
        Paginated list of plans in your organisation.

        Filters combine with AND semantics. Repeatable filters
        (`operator_ids`, `compatibility_zones`) match any of the provided
        values. `min/max` ranges are inclusive. `search` does a
        case-insensitive substring match across plan name, description, and
        id. `for_link_id` is a convenience filter — resolves the link's
        compatibility zones and applies them; if `compatibility_zones` is
        also provided, the link-derived set takes precedence (the service
        overwrites the kwarg).
      operationId: list_plans_plans_get
      security:
      - bearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/PlanSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently updated first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently updated first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: base_throttle
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/ThrottleSpeed'
          - type: 'null'
          description: Filter by base throttle speed.
          title: Base Throttle
        description: Filter by base throttle speed.
      - name: reactivation_trigger
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/ReactivationTrigger'
          - type: 'null'
          description: Filter by reactivation trigger type.
          title: Reactivation Trigger
        description: Filter by reactivation trigger type.
      - name: coverage_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          description: Filter to plans using this coverage.
          title: Coverage Id
        description: Filter to plans using this coverage.
      - name: operator_ids
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              type: string
              format: uuid
          - type: 'null'
          description: Filter to plans whose coverage includes any of these operator ids. Repeatable.
          title: Operator Ids
        description: Filter to plans whose coverage includes any of these operator ids. Repeatable.
      - name: compatibility_zones
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              $ref: '#/components/schemas/CompatibilityZone'
          - type: 'null'
          description: Filter by compatibility zone (matched against the plan's coverage). Repeatable.
          title: Compatibility Zones
        description: Filter by compatibility zone (matched against the plan's coverage). Repeatable.
      - name: min_included_data_mb
        in: query
        required: false
        schema:
          anyOf:
          - type: integer
            minimum: 0
          - type: 'null'
          description: Minimum bundled data, in MB.
          title: Min Included Data Mb
        description: Minimum bundled data, in MB.
      - name: max_included_data_mb
        in: query
        required: false
        schema:
          anyOf:
          - type: integer
            minimum: 0
          - type: 'null'
          description: Maximum bundled data, in MB.
          title: Max Included Data Mb
        description: Maximum bundled data, in MB.
      - name: min_validity_days
        in: query
        required: false
        schema:
          anyOf:
          - type: integer
            minimum: 1
          - type: 'null'
          description: Minimum validity, in days.
          title: Min Validity Days
        description: Minimum validity, in days.
      - name: max_validity_days
        in: query
        required: false
        schema:
          anyOf:
          - type: integer
            minimum: 1
          - type: 'null'
          description: Maximum validity, in days.
          title: Max Validity Days
        description: Maximum validity, in days.
      - name: search
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Free-text match against plan name, description, or id.
          title: Search
        description: Free-text match against plan name, description, or id.
      - name: for_link_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          description: Return only plans compatible with the given link (matched via compatibility zones).
          title: For Link Id
        description: Return only plans compatible with the given link (matched via compatibility zones).
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_PlanResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    post:
      tags:
      - Plans
      summary: Create a plan
      description: |-
        Create a plan from a coverage plus usage / activation parameters.

        The supplied `name` is stored locally and never sent to the
        upstream system — the upstream side gets a synthesized id, so a
        later rename (via PATCH) doesn't propagate. Use `notifications` to
        seed the initial set; further notifications can be added via
        `POST /plans/{plan_id}/notifications`.

        `reactivation_trigger` presence is fixed at creation: a plan
        created without one cannot have one added later, and vice versa.
      operationId: create_plan_plans_post
      security:
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanCreateRequest'
      responses:
        '201':
          description: Plan created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Coverage not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /plans/{plan_id}:
    servers: *id001
    get:
      tags:
      - Plans
      summary: Get a plan
      description: Fetch a single plan by id within the caller's organisation.
      operationId: get_plan_plans__plan_id__get
      security:
      - bearerAuth: []
      parameters:
      - name: plan_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Plan Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Plan not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Plan has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    patch:
      tags:
      - Plans
      summary: Update a plan
      description: |-
        Patch a plan.

        Only fields **explicitly present** in the payload are forwarded —
        `payload.model_fields_set` is used to distinguish "field omitted"
        from "field present with `null`". Sending `null` for a nullable
        field (`description`, `data_cap`, `max_reactivations`) clears it;
        omitting it leaves it unchanged.

        Validation rules surface as 422 `validation_error`:
        - `reactivation_trigger` presence is immutable (cannot add to a
          trigger-less plan, cannot remove from one that has one).
        - `data_cap.threshold_mb` cannot exceed the effective
          `included_data_mb` (post-merge with the current plan).
      operationId: update_plan_plans__plan_id__patch
      security:
      - bearerAuth: []
      parameters:
      - name: plan_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Plan Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanUpdateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Plan or new coverage not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Plan or new coverage has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
    delete:
      tags:
      - Plans
      summary: Delete a plan
      description: |-
        Soft-delete a plan.

        The upstream resource is deleted and the plan is marked `REMOVED`
        locally — subsequent reads return 410. Notifications attached to
        the plan are NOT individually cleaned up (the upstream system drops
        them with the plan; locally they persist alongside the soft-deleted
        plan row but become unreachable).

        A plan that still has active activations or live subscribers cannot
        be deleted (422).

        Returns **204 No Content** — same reasoning as `DELETE /coverages/{id}`:
        we deliberately don't expose `status` on the wire, so a returned
        body here would look identical to a pre-delete GET.
      operationId: delete_plan_plans__plan_id__delete
      security:
      - bearerAuth: []
      parameters:
      - name: plan_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Plan Id
      responses:
        '204':
          description: Plan deleted.
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Plan not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Plan has already been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /plans/{plan_id}/notifications:
    servers: *id001
    post:
      tags:
      - Plans
      summary: Add a notification to a plan
      description: |-
        Add an SMS notification to a plan.

        At most one notification per `type` per plan — duplicates return 409.
        Threshold rules are validated against the type (see
        `PlanNotificationCreateRequest`); mismatches return 422. The full
        updated plan is returned so the caller can see the new notification
        with its assigned `id`.
      operationId: add_plan_notification_plans__plan_id__notifications_post
      security:
      - bearerAuth: []
      parameters:
      - name: plan_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Plan Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanNotificationCreateRequest'
      responses:
        '201':
          description: Notification added.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Plan not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '409':
          description: A notification of this type already exists on the plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity already exists: name=example'
                code: duplicate_entity
                details:
                  entity: Entity
                  identifiers:
                    name: example
        '410':
          description: Plan has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /plans/{plan_id}/notifications/{notification_id}:
    servers: *id001
    patch:
      tags:
      - Plans
      summary: Update a plan notification
      description: |-
        Replace a notification's `message` and `threshold`.

        `type` is fixed at creation — to change the type, delete and re-add.
        The threshold is validated against the existing notification's type:
        relative ∈ [50, 100], absolute > 0, lifecycle types reject any
        threshold. Mismatches return 422.
      operationId: update_plan_notification_plans__plan_id__notifications__notification_id__patch
      security:
      - bearerAuth: []
      parameters:
      - name: plan_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Plan Id
      - name: notification_id
        in: path
        required: true
        schema:
          type: integer
          title: Notification Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanNotificationUpdateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Plan or notification not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Plan has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
    delete:
      tags:
      - Plans
      summary: Remove a notification from a plan
      description: |-
        Remove a notification from a plan.

        Returns 204 No Content — same convention as other delete endpoints.
      operationId: remove_plan_notification_plans__plan_id__notifications__notification_id__delete
      security:
      - bearerAuth: []
      parameters:
      - name: plan_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Plan Id
      - name: notification_id
        in: path
        required: true
        schema:
          type: integer
          title: Notification Id
      responses:
        '204':
          description: Notification removed.
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Plan or notification not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Plan has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links:
    servers: *id001
    get:
      tags:
      - Links
      summary: List links
      description: |-
        Paginated list of links in your organisation.

        Filters combine with AND semantics; repeatable filters
        (`compatibility_zones`, `countries`) match any of the provided
        values. `search` does a case-insensitive substring match across
        link name, id (as text), and the bound SIM's iccid.

        Removed links are excluded from the default listing — once deleted
        they only surface via the detail endpoint (which returns 410).
      operationId: list_links_links_get
      security:
      - bearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/LinkSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently updated first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently updated first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/LinkStatus'
          - type: 'null'
          description: Filter by link status.
          title: Status
        description: Filter by link status.
      - name: has_sim
        in: query
        required: false
        schema:
          anyOf:
          - type: boolean
          - type: 'null'
          description: Filter to links with (`true`) or without (`false`) a bound SIM.
          title: Has Sim
        description: Filter to links with (`true`) or without (`false`) a bound SIM.
      - name: has_activations
        in: query
        required: false
        schema:
          anyOf:
          - type: boolean
          - type: 'null'
          description: Filter to links that currently have (`true`) or don't have (`false`) active activations.
          title: Has Activations
        description: Filter to links that currently have (`true`) or don't have (`false`) active activations.
      - name: sim_type
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/SIMType'
          - type: 'null'
          description: Filter by the bound SIM's physical type (physical / eSIM).
          title: Sim Type
        description: Filter by the bound SIM's physical type (physical / eSIM).
      - name: compatibility_zones
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              $ref: '#/components/schemas/CompatibilityZone'
          - type: 'null'
          description: Filter by compatibility zone (derived from the bound SIM). Repeatable.
          title: Compatibility Zones
        description: Filter by compatibility zone (derived from the bound SIM). Repeatable.
      - name: plan_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          description: Filter to links with an active activation on this plan.
          title: Plan Id
        description: Filter to links with an active activation on this plan.
      - name: coverage_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          description: Filter to links with an active activation on a plan tied to this coverage.
          title: Coverage Id
        description: Filter to links with an active activation on a plan tied to this coverage.
      - name: countries
        in: query
        required: false
        schema:
          anyOf:
          - type: array
            items:
              type: string
          - type: 'null'
          description: Filter by the bound SIM's compatible countries (alpha-3 codes). Repeatable.
          title: Countries
        description: Filter by the bound SIM's compatible countries (alpha-3 codes). Repeatable.
      - name: network_country
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Filter by last-known network country (alpha-3).
          title: Network Country
        description: Filter by last-known network country (alpha-3).
      - name: network_connected
        in: query
        required: false
        schema:
          anyOf:
          - type: boolean
          - type: 'null'
          description: Filter to links currently observed as connected to a network.
          title: Network Connected
        description: Filter to links currently observed as connected to a network.
      - name: search
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Free-text match against link name, id, or the bound SIM's iccid.
          title: Search
        description: Free-text match against link name, id, or the bound SIM's iccid.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_LinkResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    post:
      tags:
      - Links
      summary: Create a link
      description: |-
        Create a link bound to a SIM, optionally activating plans on it.

        The SIM determines which provider the link belongs to. Supplying
        `plan_ids` activates those plans at creation; omit them to create a
        pre-active link that's waiting for its first activation.
      operationId: create_link_links_post
      security:
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkCreateRequest'
      responses:
        '201':
          description: Link created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: SIM or plan not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: SIM or plan has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: SIM is already linked, or supplied plans are incompatible with the SIM.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/bulk-delink-sim:
    servers: *id001
    post:
      tags:
      - Links
      summary: Detach the bound SIM from multiple links
      description: Delink the SIM from each of the supplied links, asynchronously.
      operationId: bulk_delink_sim_links_bulk_delink_sim_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkBulkRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /links/bulk-delete:
    servers: *id001
    post:
      tags:
      - Links
      summary: Delete multiple links
      description: |-
        Soft-delete each of the supplied links, asynchronously.

        Mirrors `DELETE /links/{id}` per item — links with a SIM still
        attached fail in-item with the same `validation_error` as the
        singular endpoint (delink first).
      operationId: bulk_delete_links_links_bulk_delete_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkBulkRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /links/bulk-suspend:
    servers: *id001
    post:
      tags:
      - Links
      summary: Suspend multiple links
      description: |-
        Suspend each of the supplied links, asynchronously.

        Mirrors `POST /links/{id}/suspend` per item — already-suspended or
        non-active links fail in-item without affecting the rest.
      operationId: bulk_suspend_links_links_bulk_suspend_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkBulkRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /links/bulk-resume:
    servers: *id001
    post:
      tags:
      - Links
      summary: Resume multiple links
      description: Resume each of the supplied suspended links, asynchronously.
      operationId: bulk_resume_links_links_bulk_resume_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkBulkRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /links/bulk-add-activation:
    servers: *id001
    post:
      tags:
      - Links
      summary: Activate a plan on multiple links
      description: |-
        Activate the given plan on each of the supplied links, asynchronously.

        Mirrors `POST /links/{id}/activations` per item — per-link
        failures (link not active, no SIM, plan/SIM zone mismatch) are
        recorded individually without stopping the rest.
      operationId: bulk_add_activation_links_bulk_add_activation_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkBulkActivationRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /links/bulk-delete-activation:
    servers: *id001
    post:
      tags:
      - Links
      summary: Remove a plan's activations from multiple links
      description: |-
        Remove the supplied plan's active activations from each link, asynchronously.

        **Differs from the singular endpoint** (`DELETE /links/{id}/activations/{activation_id}`):
        in bulk, activations are identified by **plan**, not by activation
        id — for each link, all active activations of the plan are
        removed. Per-link absence of the plan fails that item only.
      operationId: bulk_delete_activation_links_bulk_delete_activation_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkBulkActivationRequest'
        required: true
      responses:
        '202':
          description: Bulk task queued. Poll `GET /tasks/{task_id}` for progress.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /links/{link_id}:
    servers: *id001
    get:
      tags:
      - Links
      summary: Get a link
      description: |-
        Fetch a single link by id.

        For active links the network connectivity status is refreshed
        from the upstream system before the response is returned — so the
        `network_status` field reflects the current attach state, not the
        last sync.
      operationId: get_link_links__link_id__get
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
    patch:
      tags:
      - Links
      summary: Update a link
      description: |-
        Patch a link's local-only fields.

        `name` and `meta` are independent — each is applied only when
        explicitly present in the payload. Both are local-only edits (no
        upstream calls). `meta` is a full replace; `{}` clears it.

        Meta validation rules (422 `validation_error`):
        - max 10 root-level keys;
        - max nesting depth 3 (root dict counts as depth 1).
      operationId: update_link_links__link_id__patch
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkUpdateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Meta exceeds size/depth limits.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    delete:
      tags:
      - Links
      summary: Delete a link
      description: |-
        Soft-delete a link.

        Deletes the upstream resource and marks the link `REMOVED` locally —
        subsequent reads return 410. Active activations on the link are
        cascaded to `REMOVED`. A link that still has a SIM attached cannot
        be deleted (422 `validation_error`); delink first.

        Returns **204 No Content** — same reasoning as `DELETE /plans/{id}`
        and `DELETE /coverages/{id}`.
      operationId: delete_link_links__link_id__delete
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '204':
          description: Link deleted.
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has already been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link still has a SIM attached — delink first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/sim:
    servers: *id001
    post:
      tags:
      - Links
      summary: Attach a SIM to a link
      description: |-
        Bind a SIM to an existing link.

        The link must not already have a SIM, and the SIM must not be
        bound to another link. The SIM's compatibility zones must
        intersect the link's — otherwise the operation is rejected (422).
        On success the link moves to `active`.
      operationId: attach_sim_links__link_id__sim_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkAttachSIMRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link or SIM not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link or SIM has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link already has a SIM attached, the SIM is already bound to another link, the SIM has no compatibility
            zones in common with the link, or the upstream system reported the SIM as unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/sim/remove:
    servers: *id001
    post:
      tags:
      - Links
      summary: Detach the SIM from a link
      description: |-
        Detach the SIM currently bound to the link.

        The link moves to `hold` and the SIM becomes free to bind to a
        different link. No body — the SIM to remove is determined by the
        link itself.
      operationId: detach_sim_links__link_id__sim_remove_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link has no SIM attached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/network-bars:
    servers: *id001
    post:
      tags:
      - Links
      summary: Bar network types on a link
      description: |-
        Bar one or more network types on a link.

        Idempotent on a per-type basis: types already barred are filtered
        out before the upstream call. If every supplied type is already
        barred, the call is rejected (422) — nothing to do.
      operationId: bar_network_types_links__link_id__network_bars_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkNetworkBarsRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Empty list, or every supplied network type is already barred.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/network-bars/remove:
    servers: *id001
    post:
      tags:
      - Links
      summary: Unbar network types on a link
      description: |-
        Remove the bar on one or more network types.

        Idempotent on a per-type basis: types that aren't currently barred
        are filtered out before the upstream call. If none of the supplied
        types are barred, the call is rejected (422) — nothing to do.
      operationId: unbar_network_types_links__link_id__network_bars_remove_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkNetworkBarsRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Empty list, or none of the supplied network types are currently barred.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/imei-lock:
    servers: *id001
    post:
      tags:
      - Links
      summary: Lock a link to its SIM's IMEI
      description: |-
        Lock the link to the IMEI of the device its SIM is currently in.

        The IMEI is read from the bound SIM — there's no body. After the
        lock the link only accepts traffic from that specific device; if
        the SIM is moved to a different device the network rejects it.
        Requires an attached SIM whose IMEI we've observed.
      operationId: lock_imei_links__link_id__imei_lock_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link is already IMEI-locked, has no SIM attached, or the bound SIM has no known IMEI.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/imei-lock/remove:
    servers: *id001
    post:
      tags:
      - Links
      summary: Remove the IMEI lock from a link
      description: |-
        Remove the IMEI lock from the link.

        After this the SIM can move between devices freely.
      operationId: unlock_imei_links__link_id__imei_lock_remove_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link is not currently IMEI-locked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/sms:
    servers: *id001
    post:
      tags:
      - Links
      summary: Send an SMS to the link
      description: |-
        Send an SMS to the SIM bound to this link.

        The message is delivered to the SIM's MSISDN. Every attempt is
        persisted — successes return 201 with the sent record; upstream
        failures still create a `failed` history row but bubble up as a
        502 to surface the problem to the caller. The link must be
        `active` and have a SIM with an MSISDN.
      operationId: send_sms_links__link_id__sms_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkSendSMSRequest'
      responses:
        '201':
          description: SMS sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SentSMSResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link is not active, has no SIM attached, or the bound SIM has no MSISDN.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream rejected the send. The attempt is still recorded in the history with status `failed` and a
            `failure_reason`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
    get:
      tags:
      - Links
      summary: List SMS sent through this link
      description: |-
        Paginated history of SMS attempts on this link.

        Includes both successful sends and failed attempts. Default order
        is most recently sent first.
      operationId: list_sms_links__link_id__sms_get
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/SentSMSSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently sent first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently sent first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_SentSMSResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /links/{link_id}/suspend:
    servers: *id001
    post:
      tags:
      - Links
      summary: Suspend a link
      description: |-
        Suspend an active link.

        Pauses connectivity on both the upstream system and locally — the
        link moves from `active` to `suspended`. Only active links can be
        suspended (422 otherwise). Resume via `POST /links/{id}/resume`.
      operationId: suspend_link_links__link_id__suspend_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link is not currently active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/resume:
    servers: *id001
    post:
      tags:
      - Links
      summary: Resume a link
      description: |-
        Resume a suspended link.

        Restores connectivity on both the upstream system and locally — the
        link moves from `suspended` back to `active`. Only suspended links
        can be resumed (422 otherwise).
      operationId: resume_link_links__link_id__resume_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link is not currently suspended.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/activations:
    servers: *id001
    get:
      tags:
      - Links
      summary: List activations on a link
      description: |-
        Paginated list of activations on a link.

        Removed activations are excluded by default — once an activation
        has been deleted (or its parent link), it stays in the database
        but is filtered out of normal listings.
      operationId: list_activations_links__link_id__activations_get
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/ActivationSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently created first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently created first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/ActivationStatus'
          - type: 'null'
          description: Filter by activation status.
          title: Status
        description: Filter by activation status.
      - name: plan_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            format: uuid
          - type: 'null'
          description: Filter to activations of a specific plan.
          title: Plan Id
        description: Filter to activations of a specific plan.
      - name: source
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/ActivationSource'
          - type: 'null'
          description: 'Filter by origin: `manual` (customer-initiated) or `resubscription` (automatic).'
          title: Source
        description: 'Filter by origin: `manual` (customer-initiated) or `resubscription` (automatic).'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_ActivationResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
    post:
      tags:
      - Links
      summary: Activate a plan on a link
      description: |-
        Activate a plan on the link.

        The link must be `active` and have a SIM attached; the plan's
        compatibility zones must intersect the SIM's. On success the new
        activation is returned with `usage` zero-valued — real numbers
        populate once the background usage-collection worker takes its
        first snapshot.
      operationId: create_activation_links__link_id__activations_post
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActivationCreateRequest'
      responses:
        '201':
          description: Activation created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActivationResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link or plan not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link or plan has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Link is not active, has no SIM attached, or the plan's compatibility zones don't intersect the SIM's.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /links/{link_id}/activations/{activation_id}:
    servers: *id001
    delete:
      tags:
      - Links
      summary: Remove an activation from a link
      description: |-
        Soft-delete an activation.

        Removes the activation upstream and marks it `REMOVED` locally.
        Returns **204 No Content** — same convention as the other delete
        endpoints in the customer router.
      operationId: delete_activation_links__link_id__activations__activation_id__delete
      security:
      - bearerAuth: []
      parameters:
      - name: link_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Link Id
      - name: activation_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Activation Id
      responses:
        '204':
          description: Activation removed.
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Link or activation not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '410':
          description: Link or activation has been deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity has been deleted: id=2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_deleted
                details:
                  entity: Entity
                  identifiers:
                    id: 2e3f4a50-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Activation does not belong to this link.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
        '502':
          description: Upstream dependency could not process the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Internal server error
                code: upstream_unavailable
  /tasks:
    servers: *id001
    get:
      tags:
      - Tasks
      summary: List bulk tasks
      description: |-
        Paginated list of bulk tasks in your organisation.

        Each row carries the task header (status + counters); the per-item
        breakdown is reachable via `GET /tasks/{id}/items`.
      operationId: list_tasks_tasks_get
      security:
      - bearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/BulkTaskSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently updated first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently updated first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: type
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/BulkTaskType'
          - type: 'null'
          description: Filter by task type.
          title: Type
        description: Filter by task type.
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/BulkTaskStatus'
          - type: 'null'
          description: Filter by task lifecycle status.
          title: Status
        description: Filter by task lifecycle status.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_BulkTaskResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /tasks/{task_id}:
    servers: *id001
    get:
      tags:
      - Tasks
      summary: Get a bulk task
      description: |-
        Fetch a single bulk task by id.

        Returns the task header only — counters, status, and timestamps.
        Per-item details are at `GET /tasks/{id}/items`.
      operationId: get_task_tasks__task_id__get
      security:
      - bearerAuth: []
      parameters:
      - name: task_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Task Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTaskResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Task not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /tasks/{task_id}/items:
    servers: *id001
    get:
      tags:
      - Tasks
      summary: List items within a bulk task
      description: |-
        Paginated per-item results within a bulk task.

        Useful for inspecting failures: filter by `status=failed` and the
        `error` field on each item explains what went wrong for that
        specific target.
      operationId: list_task_items_tasks__task_id__items_get
      security:
      - bearerAuth: []
      parameters:
      - name: task_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
          title: Task Id
      - name: page
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          description: Page number (1-based).
          default: 1
          title: Page
        description: Page number (1-based).
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          maximum: 500
          minimum: 1
          description: Items per page.
          default: 25
          title: Page Size
        description: Items per page.
      - name: sort_by
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/BulkTaskItemSortField'
          - type: 'null'
          description: Field to sort by. Omit for default ordering (most recently created first).
          title: Sort By
        description: Field to sort by. Omit for default ordering (most recently created first).
      - name: sort_order
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/SortOrder'
          description: Sort direction.
          default: desc
        description: Sort direction.
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - $ref: '#/components/schemas/BulkTaskItemStatus'
          - type: 'null'
          description: Filter by item status.
          title: Status
        description: Filter by item status.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagedResponse_BulkTaskItemResponse_'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: Task not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
  /org:
    servers: *id001
    get:
      tags:
      - Organization
      summary: Get your organisation
      description: |-
        Return your organisation's basic info.

        Resolved from the JWT — no body, no parameters. Useful for
        confirming which tenant the current token is bound to and for
        referencing the org id in support tickets.
      operationId: get_organization_org_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CallerOrganizationResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '422':
          description: Request input is invalid, or a business rule / required provider configuration prevents the operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /org/balance:
    servers: *id001
    get:
      tags:
      - Organization
      summary: Get your latest balance snapshot
      description: |-
        Return the most recently collected main-account balance.

        Balances are an append-only ledger refreshed by a background worker
        every ~3 hours, with a skip-unchanged rule — so two snapshots with
        different `collected_at` reflect a real movement. Until the first
        collection runs, this endpoint returns **404**.
      operationId: get_balance_org_balance_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrganizationBalanceResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: No balance snapshot collected yet for your organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request input is invalid, or a business rule / required provider configuration prevents the operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
      security:
      - bearerAuth: []
  /org/cost:
    servers: *id001
    get:
      tags:
      - Organization
      summary: Get your latest monthly cost snapshot
      description: |-
        Return the latest collected cost snapshot for the given month.

        Snapshots are append-only — the `collect_costs` worker writes a new
        row on every run (every 3h) once costs have started accruing.
        This endpoint always returns the most recent snapshot for the
        requested `(year, month)`; **404** until the worker has collected
        at least one row for that month.

        Fees in the response are org-wide totals; per-operator attribution
        lives inside `usage_lines`.
      operationId: get_cost_org_cost_get
      security:
      - bearerAuth: []
      parameters:
      - name: year
        in: query
        required: true
        schema:
          type: integer
          maximum: 9999
          minimum: 2000
          description: Year (4-digit).
          title: Year
        description: Year (4-digit).
      - name: month
        in: query
        required: true
        schema:
          type: integer
          maximum: 12
          minimum: 1
          description: Month (1-12).
          title: Month
        description: Month (1-12).
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrganizationCostResponse'
        '401':
          description: Missing or invalid Authorization header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Not authenticated
                code: unauthorized
        '403':
          description: Caller is authenticated but lacks the required role or organisation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Access denied: requires ''superadmin'' role'
                code: forbidden
        '404':
          description: No cost snapshot collected for the requested month yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: 'Entity not found: id=8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff'
                code: entity_not_found
                details:
                  entity: Entity
                  identifiers:
                    id: 8a4b6df0-aaaa-bbbb-cccc-ddddeeeeffff
        '422':
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation_error:
                  summary: Business rule violation.
                  value:
                    message: Operation not allowed in current state
                    code: validation_error
                request_validation_error:
                  summary: Request input failed schema validation (bad path/query/body).
                  value:
                    message: Request validation failed
                    code: request_validation_error
                    details:
                      errors:
                      - type: uuid_parsing
                        loc:
                        - path
                        - org_id
                        msg: Input should be a valid UUID
                        input: not-a-uuid
