openapi: 3.0.3
info:
    title: SMAO Distributor API
    version: 2.0.0
    description: |
        Organization-centric Distributor API (v2) — the current contract for new
        marketplace and distribution-partner integrations. The first-generation
        partner-based contract remains available as the supported compatibility
        contract at `/api/distributor/v1`.

        The public hierarchy is organization-centric:

        Distributor -> MSP organization -> Customer organization

        There is no public partner resource. MSPs and customers are normal SMAO organizations distinguished by `type`.
        A customer organization is linked to its MSP with `parentOrganizationId`.

        License behavior is explicit:

        - MSP organizations are created with a purchased marketplace license.
        - Customer organizations require an already available `externalLicenseId`.
        - Disabled licenses block the assigned organization.
        - Reassigning or deleting a customer releases the previous license.
        - The tree endpoint returns hierarchy only; usage stays on usage endpoints.

        ## Authentication & keys

        All endpoints except `/health` require a distributor API key sent as
        `Authorization: Bearer <key>`. Keys are issued by SMAO during onboarding
        (there is no self-service issuance); the raw key is shown exactly once at
        creation — store it in a secret manager. Keys are stored hashed and carry
        a v2 version assignment and a fixed, immutable permission set: `organizations:read`,
        `organizations:write`, `usage:read`. Keys expire (default 365 days);
        request a replacement key from SMAO before expiry and switch over — old
        and new keys can overlap during an agreed rotation window. A key issued
        for v1 returns `403 API_VERSION_NOT_ALLOWED` on v2.

        ## Rate limiting

        Requests are limited per key (default 60 requests/minute; a different
        limit may be configured for your key). Every authenticated response
        carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
        `X-RateLimit-Reset` (unix seconds), and `Retry-After` (seconds until the
        window resets), including responses rejected after authentication.
        requests rejected by authentication itself carry none of these headers.
        Exceeding the limit returns `429` with `errorCode: RATE_LIMITED`.
        Rate limiting fails closed: if the rate-limit store is unavailable the
        API returns `503` with `errorCode: RATE_LIMIT_UNAVAILABLE` — retry with
        backoff.

        ## Idempotency & retries

        There is no idempotency-key header. Retrying a successful
        `POST /organizations` with the same
        `externalOrganizationId` fails safely with `409`
        `DUPLICATE_EXTERNAL_ORGANIZATION_ID` — treat that as "already created"
        when reconciling. Other notable conflicts: `LICENSE_NOT_AVAILABLE` and
        `ORGANIZATION_HAS_CHILDREN` (deleting an MSP with customers).

        ## Integration mode

        The API is poll-based: read `/organizations`, `/usage` and
        `/organizations/{organizationId}/billing-summary` on your own schedule.
        There are no outbound webhooks or event pushes yet.

servers:
    - url: https://api.smao.ai/api/distributor/v2
      description: Production
    - url: https://api2.smao.ai/api/distributor/v2
      description: Development
    - url: http://localhost:3000/api/distributor/v2
      description: Local development

tags:
    - name: Health
      description: Public connectivity check.
    - name: Plans
      description: Plans available for distributor-managed licenses.
    - name: Organizations
      description: MSP and customer organization lifecycle and hierarchy.
    - name: Licenses
      description: Marketplace license inventory, assignment, and state.
    - name: Usage
      description: Per-organization and aggregate usage reporting.
    - name: Billing
      description: Billing summaries for external distributor invoicing.

security:
    - DistributorApiKey: []

paths:
    /health:
        get:
            tags: [Health]
            operationId: getDistributorHealth
            summary: Health check
            description: Public unauthenticated service health endpoint.
            security: []
            responses:
                "200":
                    description: Service is reachable.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/HealthResponse"

    /plans:
        get:
            tags: [Plans]
            operationId: listDistributorPlans
            summary: List assignable plans
            description: Returns plans that may be assigned to distributor-managed MSP and customer organizations.
            responses:
                "200":
                    description: Assignable plans.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/PlansResponse"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations:
        get:
            tags: [Organizations]
            operationId: listDistributorOrganizations
            summary: List organizations
            description: Lists MSP and customer organizations managed by this distributor key. Use `type=msp` or `type=customer` to filter.
            parameters:
                - $ref: "#/components/parameters/Page"
                - $ref: "#/components/parameters/Limit"
                - $ref: "#/components/parameters/OrganizationTypeFilter"
                - $ref: "#/components/parameters/ExternalOrganizationIdFilter"
                - $ref: "#/components/parameters/ParentOrganizationIdFilter"
            responses:
                "200":
                    description: Organizations.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationListResponse"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "500":
                    $ref: "#/components/responses/InternalError"
        post:
            tags: [Organizations]
            operationId: createDistributorOrganization
            summary: Create MSP or customer organization
            description: |
                Creates either an MSP organization or a customer organization.

                MSP creation requires `owner` and an initial `license` whose status is
                `available` (or omitted, which defaults to `available`). The owner invitation
                is created and email delivery is best-effort; the response includes
                `owner.invitationId`.

                Customer creation requires `parentOrganizationId` and an available
                `externalLicenseId`. No customer admin is invited automatically, and `owner`
                is rejected for customer requests.
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/CreateOrganizationRequest"
                        examples:
                            msp:
                                summary: MSP organization
                                value:
                                    type: msp
                                    externalOrganizationId: acme-msp-1000
                                    organization:
                                        name: MSP Hamburg GmbH
                                        industry: IT Services
                                        timezone: Europe/Berlin
                                    owner:
                                        email: admin@msp.example
                                        firstName: Ada
                                        lastName: Admin
                                    license:
                                        externalLicenseId: lic-msp-1000
                                        status: available
                                        plan:
                                            slug: business
                                            commitment: monthly
                            customer:
                                summary: Customer organization
                                value:
                                    type: customer
                                    externalOrganizationId: acme-customer-4711
                                    parentOrganizationId: 665abc123def456789012345
                                    externalLicenseId: lic-customer-4711
                                    organization:
                                        name: Kunde 1.1 GmbH
                                        industry: Manufacturing
                                        timezone: Europe/Berlin
            responses:
                "201":
                    description: Organization created.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "409":
                    $ref: "#/components/responses/Conflict"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/tree:
        get:
            tags: [Organizations]
            operationId: getDistributorOrganizationTree
            summary: Get organization tree
            description: Returns MSP organizations with their direct customer children. This endpoint intentionally does not include usage.
            responses:
                "200":
                    description: Organization hierarchy.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationTreeResponse"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}:
        get:
            tags: [Organizations]
            operationId: getDistributorOrganization
            summary: Get organization
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            responses:
                "200":
                    description: Organization details.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "500":
                    $ref: "#/components/responses/InternalError"
        patch:
            tags: [Organizations]
            operationId: updateDistributorOrganization
            summary: Update organization metadata
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/UpdateOrganizationRequest"
            responses:
                "200":
                    description: Updated organization.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "500":
                    $ref: "#/components/responses/InternalError"
        delete:
            tags: [Organizations]
            operationId: deleteDistributorOrganization
            summary: Delete organization
            description: Deletes a customer organization and releases its license. MSP organizations may be deleted only after all child customers are removed.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            responses:
                "200":
                    description: Organization deleted.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/DeleteOrganizationResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/licenses:
        get:
            tags: [Licenses]
            operationId: listDistributorOrganizationLicenses
            summary: List MSP-owned licenses
            description: Lists licenses owned by an MSP organization. Customer organization IDs return 404.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
                - $ref: "#/components/parameters/LicenseStatusFilter"
            responses:
                "200":
                    description: Licenses.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/LicenseListResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "500":
                    $ref: "#/components/responses/InternalError"
        patch:
            tags: [Licenses]
            operationId: updateDistributorOrganizationLicenses
            summary: Upsert MSP license
            description: Creates or updates a marketplace license owned by an MSP organization. Disabled assigned licenses block their assigned organization.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/LicenseUpsertRequest"
            responses:
                "200":
                    description: Upserted license.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/LicenseResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/license:
        patch:
            tags: [Licenses]
            operationId: updateDistributorOrganizationLicense
            summary: Reassign organization license
            description: Assigns an available license to this MSP or customer organization and releases the previous license.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/LicenseAssignmentRequest"
            responses:
                "200":
                    description: Organization with assigned license.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/usage:
        get:
            tags: [Usage]
            operationId: getDistributorOrganizationUsage
            summary: Get organization usage
            description: Returns usage for one MSP or customer organization.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
            responses:
                "200":
                    description: Usage.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationUsageResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/billing-summary:
        get:
            tags: [Billing]
            operationId: getDistributorOrganizationBillingSummary
            summary: Get organization billing summary
            description: Returns usage and current license plan for one MSP or customer organization. Historical plan-window proration is not performed.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
            responses:
                "200":
                    description: Billing summary.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/BillingSummaryResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "500":
                    $ref: "#/components/responses/InternalError"

    /usage:
        get:
            tags: [Usage]
            operationId: getDistributorUsage
            summary: Get aggregate usage
            description: Returns aggregate usage across MSP and customer organizations. Use `parentOrganizationId` to include one MSP and its direct customers.
            parameters:
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
                - $ref: "#/components/parameters/ParentOrganizationIdFilter"
            responses:
                "200":
                    description: Aggregate usage.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/AggregateUsageResponse"
                "400":
                    $ref: "#/components/responses/ValidationError"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "500":
                    $ref: "#/components/responses/InternalError"

components:
    securitySchemes:
        DistributorApiKey:
            type: http
            scheme: bearer
            bearerFormat: Distributor API key

    parameters:
        OrganizationId:
            name: organizationId
            in: path
            required: true
            schema:
                type: string
                pattern: "^[a-fA-F0-9]{24}$"
        Page:
            name: page
            in: query
            schema:
                type: integer
                minimum: 1
                default: 1
        Limit:
            name: limit
            in: query
            schema:
                type: integer
                minimum: 1
                maximum: 100
                default: 50
        OrganizationTypeFilter:
            name: type
            in: query
            schema:
                type: string
                enum: [msp, customer]
        ExternalOrganizationIdFilter:
            name: externalOrganizationId
            in: query
            schema:
                type: string
        ParentOrganizationIdFilter:
            name: parentOrganizationId
            in: query
            schema:
                type: string
                pattern: "^[a-fA-F0-9]{24}$"
        LicenseStatusFilter:
            name: status
            in: query
            schema:
                type: string
                enum: [available, assigned, disabled]
        FromDate:
            name: from
            in: query
            required: true
            description: Start date in YYYY-MM-DD. Future dates are rejected. Usage endpoints allow up to 365 days; billing-summary allows up to 31 days.
            schema:
                type: string
                format: date
        ToDate:
            name: to
            in: query
            required: true
            description: End date in YYYY-MM-DD. Future dates are rejected. Usage endpoints allow up to 365 days; billing-summary allows up to 31 days.
            schema:
                type: string
                format: date

    responses:
        Unauthorized:
            description: Missing or invalid distributor API key.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        Forbidden:
            description: API key is not authorized for this version or lacks the required permission.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        NotFound:
            description: Resource not found in this distributor scope.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        ValidationError:
            description: Validation failed.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        Conflict:
            description: Conflict, such as a duplicate identifier, unavailable license, or concurrent license update.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        InternalError:
            description: Unexpected server error. Reconcile resource state before retrying a mutation.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        RateLimited:
            description: >
                Rate limit exceeded for this API key (default 60 requests/minute).
                Wait Retry-After seconds before retrying.
            headers:
                X-RateLimit-Limit:
                    schema: { type: integer }
                    description: Request limit per window for this key.
                X-RateLimit-Remaining:
                    schema: { type: integer }
                    description: Requests remaining in the current window.
                X-RateLimit-Reset:
                    schema: { type: integer }
                    description: Unix timestamp (seconds) when the window resets.
                Retry-After:
                    schema: { type: integer }
                    description: Seconds until the window resets.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
                    example:
                        status: error
                        errorCode: RATE_LIMITED
                        message: Too many requests for this API key.
        RateLimitUnavailable:
            description: >
                Rate limiting fails closed — the rate-limit store is unavailable.
                Retry with backoff.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
                    example:
                        status: error
                        errorCode: RATE_LIMIT_UNAVAILABLE
                        message: Rate limit storage is unavailable. Try again later.

    schemas:
        SuccessResponse:
            type: object
            required: [status]
            properties:
                status:
                    type: string
                    enum: [success]

        ErrorResponse:
            type: object
            required: [status, errorCode, message]
            properties:
                status:
                    type: string
                    enum: [error]
                errorCode:
                    type: string
                    enum:
                        - VALIDATION_ERROR
                        - MISSING_API_KEY
                        - INVALID_API_KEY
                        - API_KEY_EXPIRED
                        - API_VERSION_NOT_ALLOWED
                        - MISSING_PERMISSION
                        - RATE_LIMITED
                        - RATE_LIMIT_UNAVAILABLE
                        - UNAUTHORIZED
                        - FORBIDDEN
                        - NOT_FOUND
                        - INVALID_ID
                        - INVALID_DATE_RANGE
                        - DATE_RANGE_TOO_LARGE
                        - INVALID_PLAN
                        - INVALID_ORGANIZATION_TYPE
                        - DUPLICATE_EXTERNAL_ORGANIZATION_ID
                        - PARENT_ORGANIZATION_NOT_FOUND
                        - ORGANIZATION_HAS_CHILDREN
                        - ORGANIZATION_CONFLICT
                        - LICENSE_NOT_AVAILABLE
                        - LICENSE_CONFLICT
                        - LICENSE_OWNERSHIP_CONFLICT
                        - SUBSCRIPTION_CREATION_FAILED
                        - PLAN_NOT_FOUND
                        - ORG_CREATION_FAILED
                        - ORG_CLEANUP_FAILED
                        - INVITATION_CREATION_FAILED
                        - INTERNAL_ERROR
                message:
                    type: string

        DeleteOrganizationResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [message]
                  properties:
                      message:
                          type: string
                          example: Organization deleted.

        HealthResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  properties:
                      service:
                          type: string
                          example: distributor-api
                      version:
                          type: string
                          example: 2.0.0
                      timestamp:
                          type: string
                          format: date-time

        PlanInput:
            type: object
            additionalProperties: false
            required: [slug, commitment]
            properties:
                slug:
                    type: string
                    minLength: 1
                    maxLength: 100
                commitment:
                    type: string
                    enum: [none, monthly, yearly]

        PlanRef:
            type: object
            required: [id, slug, commitment]
            properties:
                id:
                    type: string
                slug:
                    type: string
                name:
                    type: string
                commitment:
                    type: string
                    enum: [none, monthly, yearly]
                currency:
                    type: string
                    description: ISO 4217 currency code for monetary values in this plan.
                baseFee:
                    type: number
                    description: Base fee in the smallest unit of `currency` (for example, cents for EUR).

        BillingPlan:
            allOf:
                - $ref: "#/components/schemas/PlanRef"
                - type: object
                  required: [name, currency, baseFee]

        AssignablePlan:
            allOf:
                - $ref: "#/components/schemas/BillingPlan"
                - type: object
                  required: [tier, usage]
                  properties:
                      tier:
                          type: integer
                          description: Numeric plan tier used for plan ordering and upgrade rules.
                      usage:
                          type: array
                          items:
                              type: object
                              required: [service, included, costPerUnit]
                              properties:
                                  service:
                                      type: string
                                  included:
                                      type: number
                                      description: Usage units included in the plan.
                                  costPerUnit:
                                      type: number
                                      description: Cost per overage unit in the smallest unit of the plan currency.

        License:
            type: object
            required: [id, externalLicenseId, status, owningOrganizationId, assignedOrganizationId, plan]
            properties:
                id:
                    type: string
                externalLicenseId:
                    type: string
                status:
                    type: string
                    enum: [available, assigned, disabled]
                owningOrganizationId:
                    type: string
                assignedOrganizationId:
                    type: string
                    nullable: true
                plan:
                    $ref: "#/components/schemas/PlanRef"
                assignedAt:
                    type: string
                    format: date-time
                    nullable: true
                disabledAt:
                    type: string
                    format: date-time
                    nullable: true
                createdAt:
                    type: string
                    format: date-time
                updatedAt:
                    type: string
                    format: date-time

        Organization:
            type: object
            required: [id, type, name, externalOrganizationId, parentOrganizationId, status]
            properties:
                id:
                    type: string
                type:
                    type: string
                    enum: [msp, customer]
                name:
                    type: string
                industry:
                    type: string
                timezone:
                    type: string
                channelPartner:
                    type: string
                externalOrganizationId:
                    type: string
                parentOrganizationId:
                    type: string
                    nullable: true
                status:
                    type: string
                    enum: [active, blocked]
                blockedReason:
                    type: string
                    nullable: true
                ownerEmail:
                    type: string
                    nullable: true
                externalLicenseId:
                    type: string
                    nullable: true
                license:
                    $ref: "#/components/schemas/License"
                    nullable: true
                createdAt:
                    type: string
                    format: date-time

        OrganizationTreeNode:
            allOf:
                - $ref: "#/components/schemas/Organization"
                - type: object
                  required: [children]
                  properties:
                      children:
                          type: array
                          items:
                              $ref: "#/components/schemas/OrganizationTreeNode"

        CreateOrganizationRequest:
            oneOf:
                - $ref: "#/components/schemas/CreateMspOrganizationRequest"
                - $ref: "#/components/schemas/CreateCustomerOrganizationRequest"

        CreateMspOrganizationRequest:
            type: object
            additionalProperties: false
            required: [type, externalOrganizationId, organization, owner, license]
            properties:
                type:
                    type: string
                    enum: [msp]
                externalOrganizationId:
                    type: string
                    minLength: 1
                    maxLength: 255
                organization:
                    $ref: "#/components/schemas/OrganizationInput"
                owner:
                    $ref: "#/components/schemas/OwnerInput"
                license:
                    $ref: "#/components/schemas/InitialLicenseRequest"

        CreateCustomerOrganizationRequest:
            type: object
            additionalProperties: false
            required: [type, externalOrganizationId, parentOrganizationId, externalLicenseId, organization]
            properties:
                type:
                    type: string
                    enum: [customer]
                externalOrganizationId:
                    type: string
                    minLength: 1
                    maxLength: 255
                parentOrganizationId:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                externalLicenseId:
                    type: string
                    minLength: 1
                    maxLength: 255
                organization:
                    $ref: "#/components/schemas/OrganizationInput"

        OrganizationInput:
            type: object
            additionalProperties: false
            required: [name, timezone]
            properties:
                name:
                    type: string
                    minLength: 1
                    maxLength: 255
                industry:
                    type: string
                    maxLength: 255
                timezone:
                    type: string
                    minLength: 1
                    maxLength: 100

        OwnerInput:
            type: object
            additionalProperties: false
            required: [email, firstName, lastName]
            properties:
                email:
                    type: string
                    format: email
                    maxLength: 255
                firstName:
                    type: string
                    minLength: 1
                    maxLength: 100
                lastName:
                    type: string
                    minLength: 1
                    maxLength: 100

        UpdateOrganizationRequest:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
                name:
                    type: string
                    minLength: 1
                    maxLength: 255
                industry:
                    type: string
                    maxLength: 255
                timezone:
                    type: string
                    minLength: 1
                    maxLength: 100
                externalOrganizationId:
                    type: string
                    minLength: 1
                    maxLength: 255

        InitialLicenseRequest:
            type: object
            additionalProperties: false
            required: [externalLicenseId, plan]
            properties:
                externalLicenseId:
                    type: string
                    minLength: 1
                    maxLength: 255
                plan:
                    $ref: "#/components/schemas/PlanInput"
                status:
                    type: string
                    enum: [available]
                    default: available
                    description: An MSP's initial license must be available so it can be assigned during creation.

        LicenseUpsertRequest:
            type: object
            additionalProperties: false
            required: [externalLicenseId, plan]
            properties:
                externalLicenseId:
                    type: string
                    minLength: 1
                    maxLength: 255
                plan:
                    $ref: "#/components/schemas/PlanInput"
                status:
                    type: string
                    enum: [available, disabled]

        LicenseAssignmentRequest:
            type: object
            additionalProperties: false
            required: [externalLicenseId]
            properties:
                externalLicenseId:
                    type: string
                    minLength: 1
                    maxLength: 255

        OrganizationResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [organization]
                  properties:
                      organization:
                          $ref: "#/components/schemas/Organization"
                      owner:
                          $ref: "#/components/schemas/OwnerInvitation"

        OwnerInvitation:
            type: object
            nullable: true
            properties:
                email:
                    type: string
                firstName:
                    type: string
                lastName:
                    type: string
                role:
                    type: string
                status:
                    type: string
                source:
                    type: string
                invitationId:
                    type: string
                expiresAt:
                    type: string
                    format: date-time

        OrganizationListResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [organizations, page, limit, total]
                  properties:
                      organizations:
                          type: array
                          items:
                              $ref: "#/components/schemas/Organization"
                      page:
                          type: integer
                      limit:
                          type: integer
                      total:
                          type: integer

        OrganizationTreeResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [organizations]
                  properties:
                      organizations:
                          type: array
                          items:
                              $ref: "#/components/schemas/OrganizationTreeNode"

        LicenseResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [license]
                  properties:
                      license:
                          $ref: "#/components/schemas/License"

        LicenseListResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [licenses]
                  properties:
                      licenses:
                          type: array
                          items:
                              $ref: "#/components/schemas/License"

        PlansResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [count, plans]
                  properties:
                      count:
                          type: integer
                      plans:
                          type: array
                          items:
                              $ref: "#/components/schemas/AssignablePlan"

        DateWindow:
            type: object
            required: [from, to]
            properties:
                from:
                    type: string
                    format: date
                to:
                    type: string
                    format: date

        UsageCalls:
            type: object
            required: [count, minutes]
            properties:
                count:
                    type: number
                minutes:
                    type: number

        UsageMessages:
            type: object
            required: [count, segments]
            properties:
                count:
                    type: number
                segments:
                    type: number

        DistributorUsage:
            type: object
            required: [calls, forwards, assistants, sms, whatsapp]
            properties:
                calls:
                    $ref: "#/components/schemas/UsageCalls"
                forwards:
                    $ref: "#/components/schemas/UsageCalls"
                assistants:
                    type: number
                sms:
                    $ref: "#/components/schemas/UsageMessages"
                whatsapp:
                    type: number

        OrganizationUsageResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [organization, period, usage]
                  properties:
                      organization:
                          $ref: "#/components/schemas/Organization"
                      period:
                          $ref: "#/components/schemas/DateWindow"
                      usage:
                          $ref: "#/components/schemas/DistributorUsage"

        AggregateUsageOrganization:
            allOf:
                - $ref: "#/components/schemas/Organization"
                - type: object
                  required: [usage]
                  properties:
                      usage:
                          $ref: "#/components/schemas/DistributorUsage"

        AggregateUsageResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [period, totals, organizationCount, organizations]
                  properties:
                      period:
                          $ref: "#/components/schemas/DateWindow"
                      totals:
                          $ref: "#/components/schemas/DistributorUsage"
                      organizationCount:
                          type: integer
                      organizations:
                          type: array
                          items:
                              $ref: "#/components/schemas/AggregateUsageOrganization"

        BillingUsageEntry:
            type: object
            required: [included, used, overage, costPerUnit, overageCost]
            properties:
                included:
                    type: number
                used:
                    type: number
                overage:
                    type: number
                costPerUnit:
                    type: number
                    description: Cost per overage unit in the smallest unit of the plan currency.
                overageCost:
                    type: number
                    description: Overage cost in the smallest unit of the plan currency.
                callCount:
                    type: number
                    nullable: true
                messageCount:
                    type: number
                    nullable: true

        BillingUsage:
            type: object
            required: [callMinutes, forwardingMinutes, sms, whatsapp, additionalAssistants]
            properties:
                callMinutes:
                    $ref: "#/components/schemas/BillingUsageEntry"
                forwardingMinutes:
                    $ref: "#/components/schemas/BillingUsageEntry"
                sms:
                    $ref: "#/components/schemas/BillingUsageEntry"
                whatsapp:
                    $ref: "#/components/schemas/BillingUsageEntry"
                additionalAssistants:
                    $ref: "#/components/schemas/BillingUsageEntry"

        BillingSummaryTotals:
            type: object
            required: [totalOverageCost, currency]
            properties:
                totalOverageCost:
                    type: number
                    description: Total overage cost in the smallest unit of `currency`.
                currency:
                    type: string
                    description: ISO 4217 currency code for all monetary values in this summary.

        BillingSummaryResponse:
            allOf:
                - $ref: "#/components/schemas/SuccessResponse"
                - type: object
                  required: [organization, window, plan, usage, totals]
                  properties:
                      organization:
                          $ref: "#/components/schemas/Organization"
                      license:
                          $ref: "#/components/schemas/License"
                          nullable: true
                      window:
                          $ref: "#/components/schemas/DateWindow"
                      plan:
                          $ref: "#/components/schemas/BillingPlan"
                      usage:
                          $ref: "#/components/schemas/BillingUsage"
                      totals:
                          $ref: "#/components/schemas/BillingSummaryTotals"
