openapi: 3.0.3
info:
    title: SMAO Distributor API v1
    version: 1.0.0
    description: |
        First-generation (v1) Distributor API — the supported compatibility
        contract for Synaxon and existing partner-based integrations. It remains
        regression-tested; new capabilities and all new distributor integrations
        use the organization-centric v2 contract at `/api/distributor/v2`.

        The Distributor API lets you manage a 3-tier reseller hierarchy on the shared SMAO instance:

        **Distributor → Partners (MSPs) → Customer Organizations**

        Your API key is tied to a **partnerCode** (e.g. "acme") and every request is automatically
        scoped to the data that belongs to your distributor account.

        ## Authentication

        Every request (except the health check) requires a Bearer token in the **Authorization** header:

        ```
        Authorization: Bearer <your-api-key>
        ```

        Example with curl:
        ```bash
        curl -H "Authorization: Bearer YOUR_API_KEY" \
             https://api2.smao.ai/api/distributor/v1/partners
        ```

        If the key is missing, invalid, or expired, the API returns **401**. v1 keys are authorized
        for this version only; a key issued for another Distributor version returns **403** with
        `API_VERSION_NOT_ALLOWED`. Missing endpoint permissions also return **403**.

        ## Rate Limits

        Each Distributor API key is limited by its configured **requestsPerMinute** value. Keys default
        to **60 requests per minute**. Authenticated responses include `X-RateLimit-Limit`,
        `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Rate-limited responses
        return **429** with `RATE_LIMITED`. If the shared rate-limit store is unavailable,
        the API fails closed with **503** and `RATE_LIMIT_UNAVAILABLE`.

        ## Quick Start

        1. **List available plans** - call the plans endpoint to see which SMAO plans you can assign.
        2. **Create a partner (MSP)** - provide a name, your external ID, and the MSP contact's email.
           SMAO creates an internal organization for the MSP. Trial and invitation
           setup are best-effort; reconcile dashboard access after provisioning.
        3. **Create customer organizations** - provide the **partnerId** from step 2, the customer details,
           owner email, and the plan to assign. SMAO provisions the organization and
           creates the customer-owner invitation; email delivery is best-effort.
        4. **Query usage and billing** - use the usage and billing-summary endpoints to get the
           data you need for your own invoicing.

        ## Hierarchy

        1. **Distributor (you)** - holds the API key, manages partners, and can see all data.
        2. **Partners (MSPs)** - Managed Service Providers or resellers beneath you. Each partner
           gets their own SMAO organization (a free trial is attempted when available) and can onboard customers
           from the SMAO dashboard. Billing is managed by the distributor.
        3. **Customer Organizations** - end-customer organizations created either by you via
           the API (with a required **partnerId**) or by the MSP from the dashboard.
           Every customer organization is linked to exactly one partner.

        ## Partner (MSP) Organizations

        When you create a partner, SMAO automatically:
        - Creates a new SMAO organization for the MSP with billing managed by the distributor.
        - Attempts to start a free trial when a trial plan is available.
        - When **contactEmail** is provided, attempts to create and send an admin invitation.

        After dashboard access is established, the MSP can create customer organizations
        directly from the SMAO dashboard. Those customer organizations are automatically linked
        to the MSP and marked as partner-managed - no API call needed.

        Billing for the MSP organization is also managed by you - MSPs cannot access Stripe
        billing, change subscriptions, or manage payment methods on the dashboard. They also
        cannot delete or leave their organization - that lifecycle is controlled by you via the API.
        MSPs never get API access.

        ## Customer Restrictions

        Customer organizations created through this API or by an MSP from the dashboard are
        partner-managed. On the SMAO dashboard, these end-customers:
        - Cannot create additional organizations.
        - Cannot leave or delete the partner-managed organization.
        - Cannot change, cancel, upgrade, or downgrade subscriptions through the dashboard.
        - Cannot access Stripe billing self-service such as payment methods or the customer portal.

        ## Billing Model

        Billing for customer organizations stays entirely with you. SMAO does not charge end-customers
        directly. Instead, you assign a plan to each customer organization and SMAO tracks their usage.

        **All monetary values use the smallest unit of the returned currency**
        (for example, 6900 = 69.00 EUR).

        Use the billing-summary endpoint to get everything you need to invoice a customer:
        - The assigned plan with its **baseFee** (the monthly reference price you invoice).
        - Per-service usage: how many units were used vs. how many are included in the plan.
        - Overage: the number of units used beyond the plan's allowance.
        - Overage cost: overage units multiplied by the per-unit cost, in the
          smallest unit of the returned currency.
        - Total overage cost across all services.

        The billing-summary date range must not exceed **31 days**. You control the billing cycle -
        we just report usage for the window you specify.

        ## Revenue Tracking

        Every customer organization carries your **channelPartner** code and a **partnerId** linking
        it to the MSP. You can query usage and billing at any level:
        - **Per customer** - billing-summary or usage for a single organization
        - **Per MSP** - aggregated usage for all organizations under a partner
        - **Across all MSPs** - global usage, optionally filtered by partnerId

        ## Error Handling

        All error responses follow the same format:
        ```json
        { "status": "error", "errorCode": "SOME_CODE", "message": "Human-readable description" }
        ```

        **Common error codes:**

        | Code | Description |
        |------|-------------|
        | MISSING_API_KEY | No Authorization header or empty Bearer token |
        | INVALID_API_KEY | Key not found or revoked |
        | API_KEY_EXPIRED | Key has passed its expiration date |
        | API_VERSION_NOT_ALLOWED | Key is not authorized for Distributor API v1 |
        | MISSING_PERMISSION | Key lacks the required permission for this endpoint |
        | RATE_LIMITED | API key exceeded its configured requestsPerMinute limit |
        | RATE_LIMIT_UNAVAILABLE | Shared rate-limit storage is unavailable; retry shortly |
        | VALIDATION_ERROR | Request body or query parameter validation failed |
        | INVALID_ID | The provided ID is not a valid 24-character hex string |
        | NOT_FOUND | The requested resource does not exist or does not belong to you |
        | DUPLICATE_EXTERNAL_PARTNER_ID | A partner with this externalPartnerId already exists |
        | DUPLICATE_EXTERNAL_CUSTOMER_ID | An organization with this externalCustomerId already exists |
        | PARTNER_NOT_FOUND | The partnerId does not reference an active partner |
        | PARTNER_HAS_ORGANIZATIONS | Cannot delete a partner that still has linked organizations |
        | INVALID_PLAN | The selected plan slug/commitment is not available |
        | PLAN_NOT_FOUND | No subscription found for this organization |
        | INVALID_DATE_RANGE | Date params are missing, malformed, or from is after to |
        | DATE_RANGE_TOO_LARGE | Date range exceeds the 31-day maximum |
        | ORG_CREATION_FAILED | The partner or customer organization could not be created |
        | INTERNAL_ERROR | Unexpected server error — retry later or contact support |

servers:
    - url: https://api.smao.ai/api/distributor/v1
      description: Production
    - url: https://api2.smao.ai/api/distributor/v1
      description: Development

tags:
    - name: Health
      description: Health and connectivity endpoints.
    - name: Partners
      description: MSP/partner lifecycle endpoints. Partners represent resellers or managed service providers beneath the distributor.
    - name: Organizations
      description: Organization lifecycle and lookup endpoints.
    - name: Usage
      description: Usage reporting endpoints for distributor billing and reconciliation.
    - name: Billing
      description: Plan assignment and billing-summary endpoints for external invoicing.

security:
    - DistributorApiKey: []

paths:
    /health:
        get:
            tags:
                - Health
            summary: Health check
            operationId: getDistributorHealth
            security: []
            responses:
                "200":
                    description: Distributor API is healthy.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/HealthResponse"

    /plans:
        get:
            tags:
                - Billing
            summary: List assignable plans
            operationId: listDistributorAssignablePlans
            description: |
                Returns all SMAO plans available for assignment to customer organizations.
                Trial and enterprise plans are excluded. Each plan includes its **slug**, **commitment**
                (monthly/yearly), **baseFee** in the smallest currency unit, and a **usage** array showing included units
                and overage cost per service.

                Use the **slug** and **commitment** values when creating or updating an organization's plan.
            responses:
                "200":
                    description: Current plans you can assign to distributor-managed organizations. Trial and enterprise plans are excluded.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/ListDistributorPlansResponse"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /partners:
        get:
            tags:
                - Partners
            summary: List partners
            operationId: listDistributorPartners
            description: |
                Returns a paginated list of all partners (MSPs) under your distributor account.
                Each partner includes an **organizationCount** showing how many customer organizations
                are linked to it. Use the **externalPartnerId** query parameter to filter by your own partner identifier.
            parameters:
                - $ref: "#/components/parameters/Page"
                - $ref: "#/components/parameters/Limit"
                - name: externalPartnerId
                  in: query
                  required: false
                  description: Exact-match filter for the distributor partner identifier.
                  schema:
                      type: string
                      minLength: 1
                      maxLength: 255
            responses:
                "200":
                    description: Partners attributed to this distributor.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/ListPartnersResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"
        post:
            tags:
                - Partners
            summary: Create partner
            operationId: createDistributorPartner
            description: |
                Creates a new partner (MSP) and automatically provisions a SMAO organization for them.

                **Side effects:**
                - A new internal SMAO organization is created with the partner's name and billing managed by the distributor.
                - A free trial is attempted when a trial plan is available; trial setup is best-effort.
                - If **contactEmail** is provided, invitation creation and email delivery are attempted as best-effort side effects.
                - The partner's **organizationId** in the response references this auto-created organization.

                After dashboard access is established, the MSP can create customer organizations from the UI.
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/CreatePartnerRequest"
            responses:
                "201":
                    description: Partner created.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/GetPartnerResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "409":
                    $ref: "#/components/responses/Conflict"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /partners/{partnerId}:
        get:
            tags:
                - Partners
            summary: Get partner
            operationId: getDistributorPartner
            parameters:
                - $ref: "#/components/parameters/PartnerId"
            responses:
                "200":
                    description: Partner details.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/GetPartnerResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"
        patch:
            tags:
                - Partners
            summary: Update partner
            operationId: updateDistributorPartner
            parameters:
                - $ref: "#/components/parameters/PartnerId"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/UpdatePartnerRequest"
            responses:
                "200":
                    description: Partner updated.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/GetPartnerResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"
        delete:
            tags:
                - Partners
            summary: Delete partner
            operationId: deleteDistributorPartner
            description: |
                Deletes a partner only when it has no linked customer organizations and no
                internal MSP organization. Partners created through v1 normally have an internal
                organization, so this operation returns `409 PARTNER_HAS_ORGANIZATIONS` until that
                organization has been removed outside this endpoint. There is no cascade delete.
            parameters:
                - $ref: "#/components/parameters/PartnerId"
            responses:
                "200":
                    description: Partner deleted.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/DeletePartnerResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /partners/{partnerId}/usage:
        get:
            tags:
                - Usage
            summary: Get aggregated usage for a single partner's organizations
            operationId: getDistributorPartnerScopedUsage
            description: |
                Returns aggregated usage across all customer organizations that belong to the
                specified partner (MSP), plus a per-organization breakdown.
            parameters:
                - $ref: "#/components/parameters/PartnerId"
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
            responses:
                "200":
                    description: Aggregated usage for one partner's organizations.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/PartnerUsageResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations:
        get:
            tags:
                - Organizations
            summary: List organizations
            operationId: listDistributorOrganizations
            description: |
                Returns a paginated list of customer organizations across all your partners.
                Use the optional **partnerId** query parameter to filter by a specific MSP.
                Use **externalCustomerId** to look up a customer by your own identifier.
            parameters:
                - $ref: "#/components/parameters/Page"
                - $ref: "#/components/parameters/Limit"
                - $ref: "#/components/parameters/ExternalCustomerIdFilter"
                - $ref: "#/components/parameters/PartnerIdFilter"
            responses:
                "200":
                    description: Organizations attributed to this partner.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/ListOrganizationsResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"
        post:
            tags:
                - Organizations
            summary: Create organization and initial owner invitation
            operationId: createDistributorOrganization
            description: |
                Creates a new customer organization under the specified partner (MSP), creates
                its owner invitation record, and attempts to send the invitation email.

                **What happens:**
                1. The organization is created with the selected plan and linked to the partner via **partnerId**.
                2. The organization is marked as partner-managed - no Stripe billing is set up for the customer.
                3. An invitation record is created for **owner.email**. Email delivery is best-effort.
                4. Once the owner accepts, they have full product access but cannot manage billing or subscriptions.

                The **externalCustomerId** must be unique across all your organizations (not just per partner).
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/CreateOrganizationRequest"
            responses:
                "201":
                    description: Organization and owner invitation record created; email delivery may still be pending or fail.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/CreateOrganizationResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "409":
                    $ref: "#/components/responses/Conflict"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}:
        get:
            tags:
                - Organizations
            summary: Get organization
            operationId: getDistributorOrganization
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            responses:
                "200":
                    description: Organization details.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/GetOrganizationResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"
        patch:
            tags:
                - Organizations
            summary: Update organization
            operationId: updateDistributorOrganization
            description: |
                Update allowed fields on a partner-managed organization.
                At least one field must be provided. If **externalCustomerId** is changed,
                uniqueness is enforced within the partner's organizations.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/UpdateOrganizationRequest"
            responses:
                "200":
                    description: Organization updated.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/GetOrganizationResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "409":
                    $ref: "#/components/responses/Conflict"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"
        delete:
            tags:
                - Organizations
            summary: Delete organization
            operationId: deleteDistributorOrganization
            description: |
                Permanently delete a partner-managed organization and all associated data
                including subscriptions, invitations, assistants, calls, contacts, knowledge,
                and S3 storage. This action is irreversible.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            responses:
                "200":
                    description: Organization deleted.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/DeleteOrganizationResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/plan:
        patch:
            tags:
                - Billing
            summary: Update assigned plan for an organization
            operationId: updateDistributorOrganizationPlan
            description: |
                Changes the plan assigned to a customer organization. The change takes effect immediately.
                The previous subscription is marked as upgraded and a new active subscription is created
                with the selected plan. If the organization is already on the requested plan, no change is made.

                Use the plans endpoint to see available plan slugs and commitment options.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/UpdateOrganizationPlanRequest"
            responses:
                "200":
                    description: Organization plan updated.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/GetOrganizationResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/billing-summary:
        get:
            tags:
                - Billing
            summary: Get billing summary for a single organization
            operationId: getDistributorOrganizationBillingSummary
            description: |
                Returns usage for the requested date window compared against the organization's
                current subscription plan. The date range must not exceed 31 days.
                Usage is compared against the plan's included allowances and any overages
                are calculated with the plan's per-unit overage cost.
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
            responses:
                "200":
                    description: Billing summary for the requested window.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationBillingSummaryResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /organizations/{organizationId}/usage:
        get:
            tags:
                - Usage
            summary: Get usage for a single organization
            operationId: getDistributorOrganizationUsage
            parameters:
                - $ref: "#/components/parameters/OrganizationId"
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
            responses:
                "200":
                    description: Usage for one organization in the requested date range.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/OrganizationUsageResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "404":
                    $ref: "#/components/responses/NotFound"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

    /usage:
        get:
            tags:
                - Usage
            summary: Get aggregated usage across all organizations
            operationId: getDistributorPartnerUsage
            description: |
                Returns aggregated usage across all customer organizations under this distributor.
                Optionally filter by **partnerId** to scope results to a single MSP's customers.
            parameters:
                - $ref: "#/components/parameters/FromDate"
                - $ref: "#/components/parameters/ToDate"
                - $ref: "#/components/parameters/PartnerIdFilter"
            responses:
                "200":
                    description: Aggregated usage for all (or filtered) customer organizations.
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/PartnerUsageResponse"
                "400":
                    $ref: "#/components/responses/BadRequest"
                "401":
                    $ref: "#/components/responses/Unauthorized"
                "403":
                    $ref: "#/components/responses/Forbidden"
                "429":
                    $ref: "#/components/responses/RateLimited"
                "503":
                    $ref: "#/components/responses/RateLimitUnavailable"
                "500":
                    $ref: "#/components/responses/InternalError"

components:
    securitySchemes:
        DistributorApiKey:
            type: http
            scheme: bearer
            bearerFormat: API Key
            description: Long-lived distributor API key scoped to a channel partner on the shared smao instance.

    parameters:
        OrganizationId:
            name: organizationId
            in: path
            required: true
            description: MongoDB ObjectId of the organization.
            schema:
                type: string
                pattern: "^[a-fA-F0-9]{24}$"
        FromDate:
            name: from
            in: query
            required: true
            description: Start date in YYYY-MM-DD format, inclusive.
            schema:
                type: string
                format: date
        ToDate:
            name: to
            in: query
            required: true
            description: End date in YYYY-MM-DD format, inclusive.
            schema:
                type: string
                format: date
        Page:
            name: page
            in: query
            required: false
            description: 1-based page number.
            schema:
                type: integer
                minimum: 1
                default: 1
        Limit:
            name: limit
            in: query
            required: false
            description: Page size.
            schema:
                type: integer
                minimum: 1
                maximum: 100
                default: 50
        ExternalCustomerIdFilter:
            name: externalCustomerId
            in: query
            required: false
            description: Exact-match filter for the distributor customer identifier.
            schema:
                type: string
                minLength: 1
                maxLength: 255
        PartnerId:
            name: partnerId
            in: path
            required: true
            description: MongoDB ObjectId of the partner (MSP).
            schema:
                type: string
                pattern: "^[a-fA-F0-9]{24}$"
        PartnerIdFilter:
            name: partnerId
            in: query
            required: false
            description: Filter results to a specific partner (MSP). Omit to include all organizations.
            schema:
                type: string
                pattern: "^[a-fA-F0-9]{24}$"

    responses:
        BadRequest:
            description: Request validation failed.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        Unauthorized:
            description: Missing, invalid, or expired API key.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        Forbidden:
            description: The API key does not have permission to access this resource.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        RateLimited:
            description: The API key exceeded its configured requests-per-minute limit.
            headers:
                X-RateLimit-Limit:
                    description: Maximum requests allowed in the current minute.
                    schema:
                        type: integer
                        example: 60
                X-RateLimit-Remaining:
                    description: Requests remaining in the current minute.
                    schema:
                        type: integer
                        example: 0
                X-RateLimit-Reset:
                    description: Unix timestamp in seconds when the current window resets.
                    schema:
                        type: integer
                        example: 1776902940
                Retry-After:
                    description: Seconds to wait before retrying.
                    schema:
                        type: integer
                        example: 60
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
                    example:
                        status: error
                        errorCode: RATE_LIMITED
                        message: Too many requests for this API key.
        RateLimitUnavailable:
            description: Shared rate-limit storage is unavailable, so the API fails closed.
            headers:
                Retry-After:
                    description: Seconds to wait before retrying.
                    schema:
                        type: integer
                        example: 30
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
                    example:
                        status: error
                        errorCode: RATE_LIMIT_UNAVAILABLE
                        message: Rate limit storage is unavailable. Try again shortly.
        NotFound:
            description: The requested resource does not exist or is not in this distributor's scope.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        Conflict:
            description: A contract conflict occurred, such as a duplicate external ID or a partner that still has organizations.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"
        InternalError:
            description: Internal server error.
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/ErrorResponse"

    schemas:
        ErrorResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - errorCode
                - message
            properties:
                status:
                    type: string
                    enum:
                        - error
                errorCode:
                    type: string
                    description: Stable machine-readable error code.
                    example: INVALID_DATE_RANGE
                message:
                    type: string
                    description: Human-readable error message.
                    example: from and to must be valid YYYY-MM-DD values

        HealthResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - service
                - version
                - timestamp
            properties:
                status:
                    type: string
                    enum:
                        - success
                service:
                    type: string
                    example: distributor-api
                version:
                    type: string
                    example: 1.0.0
                timestamp:
                    type: string
                    format: date-time

        OrganizationCreateInput:
            type: object
            additionalProperties: false
            required:
                - name
                - timezone
            properties:
                name:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Display name of the organization.
                    example: Acme GmbH
                industry:
                    type: string
                    maxLength: 255
                    description: Optional industry label. Can be omitted or left empty.
                    example: IT Services
                timezone:
                    type: string
                    minLength: 1
                    maxLength: 100
                    description: IANA timezone identifier for the organization.
                    example: Europe/Berlin

        OrganizationOwnerInput:
            type: object
            additionalProperties: false
            required:
                - email
                - firstName
                - lastName
            properties:
                email:
                    type: string
                    format: email
                    maxLength: 255
                    description: Email address used for the required owner invitation record. Email delivery is best-effort.
                    example: owner@example.com
                firstName:
                    type: string
                    minLength: 1
                    maxLength: 100
                    description: First name of the organization owner.
                    example: Alice
                lastName:
                    type: string
                    minLength: 1
                    maxLength: 100
                    description: Last name of the organization owner.
                    example: Müller

        CreateOrganizationRequest:
            type: object
            additionalProperties: false
            required:
                - organization
                - owner
                - externalCustomerId
                - partnerId
                - plan
            properties:
                organization:
                    $ref: "#/components/schemas/OrganizationCreateInput"
                owner:
                    $ref: "#/components/schemas/OrganizationOwnerInput"
                externalCustomerId:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Your unique customer identifier on the distributor side.
                    example: SYN-4711
                externalContractId:
                    type: string
                    nullable: true
                    maxLength: 255
                    description: Optional contract identifier on the distributor side.
                    example: CON-2026-0001
                partnerId:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                    description: ID of the partner (MSP) this organization belongs to. Must reference an active partner.
                plan:
                    $ref: "#/components/schemas/DistributorPlanSelection"
            example:
                organization:
                    name: Acme GmbH
                    timezone: Europe/Berlin
                owner:
                    email: owner@example.com
                    firstName: Alice
                    lastName: Müller
                externalCustomerId: SYN-4711
                partnerId: 665abc123def456789012345
                plan:
                    slug: advanced
                    commitment: monthly

        UpdateOrganizationRequest:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
                name:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Display name of the organization.
                    example: Acme GmbH
                industry:
                    type: string
                    maxLength: 255
                    description: Industry label. Send empty string to clear.
                    example: IT Services
                timezone:
                    type: string
                    minLength: 1
                    maxLength: 100
                    description: IANA timezone identifier.
                    example: Europe/Berlin
                externalCustomerId:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Your unique customer identifier. Must be unique within your partner scope.
                    example: SYN-4711
                externalContractId:
                    type: string
                    nullable: true
                    maxLength: 255
                    description: Optional contract identifier. Send null to clear.
                    example: CON-2026-0001
            example:
                name: Acme GmbH (renamed)
                externalContractId: CON-2026-0002

        DeleteOrganizationResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - message
            properties:
                status:
                    type: string
                    enum:
                        - success
                message:
                    type: string
                    example: Organization deleted.

        CreatePartnerRequest:
            type: object
            additionalProperties: false
            required:
                - name
                - externalPartnerId
            properties:
                name:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Display name of the partner (MSP).
                    example: MSP Hamburg GmbH
                externalPartnerId:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Your unique identifier for this partner. Must be unique within your distributor scope.
                    example: MSP-001
                contactEmail:
                    type: string
                    format: email
                    maxLength: 255
                    description: Primary contact email for this partner.
                    example: admin@msp-hamburg.de
                contactName:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Primary contact name.
                    example: Hans Mueller
                notes:
                    type: string
                    maxLength: 1000
                    nullable: true
                    description: Internal notes about this partner.
                timezone:
                    type: string
                    maxLength: 100
                    description: IANA timezone for the partner's internal MSP organization. Defaults to Europe/Berlin.
                    example: Europe/Berlin

        UpdatePartnerRequest:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
                name:
                    type: string
                    minLength: 1
                    maxLength: 255
                externalPartnerId:
                    type: string
                    minLength: 1
                    maxLength: 255
                contactEmail:
                    type: string
                    format: email
                    maxLength: 255
                    nullable: true
                contactName:
                    type: string
                    minLength: 1
                    maxLength: 255
                    nullable: true
                notes:
                    type: string
                    maxLength: 1000
                    nullable: true

        DistributorPartner:
            type: object
            additionalProperties: false
            required:
                - id
                - name
                - externalPartnerId
                - isActive
                - createdAt
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                name:
                    type: string
                externalPartnerId:
                    type: string
                contactEmail:
                    type: string
                    nullable: true
                    format: email
                contactName:
                    type: string
                    nullable: true
                isActive:
                    type: boolean
                notes:
                    type: string
                    nullable: true
                organizationId:
                    type: string
                    nullable: true
                    pattern: "^[a-fA-F0-9]{24}$"
                    description: The MSP's own SMAO organization ID. Created automatically when the partner is created.
                organizationCount:
                    type: integer
                    minimum: 0
                    description: Number of customer organizations linked to this partner. Present on list and get responses.
                createdAt:
                    type: string
                    format: date-time
            example:
                id: "665abc123def456789012345"
                name: "MSP Hamburg GmbH"
                externalPartnerId: "MSP-001"
                contactEmail: "admin@msp-hamburg.de"
                contactName: "Hans Mueller"
                isActive: true
                notes: null
                organizationId: "665abc123def456789012346"
                organizationCount: 12
                createdAt: "2026-03-20T10:41:14.553Z"

        GetPartnerResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - partner
            properties:
                status:
                    type: string
                    enum:
                        - success
                partner:
                    $ref: "#/components/schemas/DistributorPartner"

        ListPartnersResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - page
                - limit
                - total
                - partners
            properties:
                status:
                    type: string
                    enum:
                        - success
                page:
                    type: integer
                    minimum: 1
                limit:
                    type: integer
                    minimum: 1
                total:
                    type: integer
                    minimum: 0
                partners:
                    type: array
                    items:
                        $ref: "#/components/schemas/DistributorPartner"

        DeletePartnerResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - message
            properties:
                status:
                    type: string
                    enum:
                        - success
                message:
                    type: string
                    example: Partner deleted.

        UpdateOrganizationPlanRequest:
            type: object
            additionalProperties: false
            required:
                - plan
            properties:
                plan:
                    $ref: "#/components/schemas/DistributorPlanSelection"
            example:
                plan:
                    slug: professional
                    commitment: yearly

        DistributorPlanSelection:
            type: object
            additionalProperties: false
            required:
                - slug
                - commitment
            properties:
                slug:
                    type: string
                    minLength: 1
                    maxLength: 100
                    description: Plan slug as returned by the plans endpoint.
                    example: advanced
                commitment:
                    type: string
                    enum:
                        - none
                        - monthly
                        - yearly
                    description: Commitment variant as returned by the plans endpoint.
                    example: monthly

        DistributorPlanSummary:
            type: object
            additionalProperties: false
            required:
                - id
                - slug
                - name
                - commitment
                - currency
                - baseFee
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                slug:
                    type: string
                name:
                    type: string
                commitment:
                    type: string
                    enum:
                        - none
                        - monthly
                        - yearly
                currency:
                    type: string
                baseFee:
                    type: integer
                    minimum: 0
                    description: Reference base fee in the smallest unit of `currency`. You invoice this on your side.

        DistributorPlanUsageEntry:
            type: object
            additionalProperties: false
            required:
                - service
                - included
                - costPerUnit
            properties:
                service:
                    type: string
                    enum:
                        - CALL
                        - FORWARD
                        - SMS
                        - WHATSAPP
                        - ASSISTANT
                included:
                    type: integer
                    minimum: 0
                costPerUnit:
                    type: integer
                    minimum: 0
                    description: Overage price per unit in the smallest unit of `currency`.

        DistributorAssignablePlan:
            type: object
            additionalProperties: false
            required:
                - id
                - slug
                - name
                - commitment
                - tier
                - currency
                - baseFee
                - usage
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                slug:
                    type: string
                name:
                    type: string
                commitment:
                    type: string
                    enum:
                        - none
                        - monthly
                        - yearly
                tier:
                    type: integer
                    minimum: 0
                currency:
                    type: string
                baseFee:
                    type: integer
                    minimum: 0
                    description: Reference base fee in the smallest unit of `currency`. You invoice this on your side.
                usage:
                    type: array
                    items:
                        $ref: "#/components/schemas/DistributorPlanUsageEntry"

        ListDistributorPlansResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - count
                - plans
            properties:
                status:
                    type: string
                    enum:
                        - success
                count:
                    type: integer
                    minimum: 0
                plans:
                    type: array
                    items:
                        $ref: "#/components/schemas/DistributorAssignablePlan"

        DistributorOrganization:
            type: object
            additionalProperties: false
            required:
                - id
                - name
                - industry
                - timezone
                - channelPartner
                - externalCustomerId
                - externalContractId
                - partnerId
                - ownerEmail
                - createdAt
                - plan
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                name:
                    type: string
                industry:
                    type: string
                timezone:
                    type: string
                channelPartner:
                    type: string
                    description: Distributor code derived from the API key (e.g. "acme").
                    example: acme
                externalCustomerId:
                    type: string
                externalContractId:
                    type: string
                    nullable: true
                partnerId:
                    type: string
                    nullable: true
                    pattern: "^[a-fA-F0-9]{24}$"
                    description: ID of the partner (MSP) this organization belongs to.
                ownerEmail:
                    type: string
                    nullable: true
                    format: email
                createdAt:
                    type: string
                    format: date-time
                plan:
                    type: object
                    nullable: true
                    allOf:
                        - $ref: "#/components/schemas/DistributorPlanSummary"

        DistributorInvitedUser:
            type: object
            additionalProperties: false
            required:
                - email
                - firstName
                - lastName
                - role
                - status
                - source
                - invitationId
                - expiresAt
            properties:
                email:
                    type: string
                    format: email
                firstName:
                    type: string
                lastName:
                    type: string
                role:
                    type: string
                    enum:
                        - admin
                        - member
                status:
                    type: string
                    enum:
                        - invited
                source:
                    type: string
                    enum:
                        - distributor-api
                invitationId:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                expiresAt:
                    type: string
                    format: date-time

        CreateOrganizationResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - organization
                - owner
            properties:
                status:
                    type: string
                    enum:
                        - success
                organization:
                    $ref: "#/components/schemas/DistributorOrganization"
                owner:
                    $ref: "#/components/schemas/DistributorInvitedUser"
            example:
                status: success
                organization:
                    id: "69c175970f3e28310926a1aa"
                    name: "Acme GmbH"
                    industry: "IT Services"
                    timezone: "Europe/Berlin"
                    channelPartner: "acme"
                    externalCustomerId: "SYN-4711"
                    externalContractId: "CON-2026-0001"
                    partnerId: "665abc123def456789012345"
                    ownerEmail: "owner@acme.de"
                    createdAt: "2026-03-23T17:17:11.605Z"
                    plan:
                        id: "6967b1c7138bed3e7f6bf7e6"
                        slug: "advanced"
                        name: "Advanced"
                        commitment: "monthly"
                        currency: "EUR"
                        baseFee: 6900
                owner:
                    email: "owner@acme.de"
                    firstName: "Alice"
                    lastName: "Müller"
                    role: "admin"
                    status: "invited"
                    source: "distributor-api"
                    invitationId: "69c175970f3e28310926a1bb"
                    expiresAt: "2026-03-30T17:17:11.814Z"

        ListOrganizationsResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - page
                - limit
                - total
                - organizations
            properties:
                status:
                    type: string
                    enum:
                        - success
                page:
                    type: integer
                    minimum: 1
                limit:
                    type: integer
                    minimum: 1
                total:
                    type: integer
                    minimum: 0
                organizations:
                    type: array
                    items:
                        $ref: "#/components/schemas/DistributorOrganization"

        GetOrganizationResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - organization
            properties:
                status:
                    type: string
                    enum:
                        - success
                organization:
                    $ref: "#/components/schemas/DistributorOrganization"

        UsagePeriod:
            type: object
            additionalProperties: false
            required:
                - from
                - to
            properties:
                from:
                    type: string
                    format: date
                to:
                    type: string
                    format: date

        UsageDateTimePeriod:
            type: object
            additionalProperties: false
            description: Legacy single-organization usage response period, serialized as UTC timestamps.
            required:
                - from
                - to
            properties:
                from:
                    type: string
                    format: date-time
                to:
                    type: string
                    format: date-time

        CountMinutesMetric:
            type: object
            additionalProperties: false
            required:
                - count
                - minutes
            properties:
                count:
                    type: integer
                    minimum: 0
                minutes:
                    type: integer
                    minimum: 0

        SmsUsageMetric:
            type: object
            additionalProperties: false
            required:
                - count
                - segments
            properties:
                count:
                    type: integer
                    minimum: 0
                segments:
                    type: integer
                    minimum: 0

        UsageMetrics:
            type: object
            additionalProperties: false
            required:
                - calls
                - forwards
                - assistants
                - sms
                - whatsapp
            properties:
                calls:
                    $ref: "#/components/schemas/CountMinutesMetric"
                forwards:
                    $ref: "#/components/schemas/CountMinutesMetric"
                assistants:
                    type: integer
                    minimum: 0
                    description: Cumulative assistant count at the end of the requested period or day.
                sms:
                    $ref: "#/components/schemas/SmsUsageMetric"
                whatsapp:
                    type: integer
                    minimum: 0

        UsageOrganizationRef:
            type: object
            additionalProperties: false
            required:
                - id
                - name
                - externalCustomerId
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                name:
                    type: string
                externalCustomerId:
                    type: string

        BillingUsageEntry:
            type: object
            additionalProperties: false
            required:
                - included
                - used
                - overage
                - costPerUnit
                - overageCost
            properties:
                included:
                    type: integer
                    minimum: 0
                    description: Units included in the plan's base fee.
                used:
                    type: integer
                    minimum: 0
                    description: Units consumed in the requested window.
                overage:
                    type: integer
                    minimum: 0
                    description: Units exceeding the included amount (used minus included, minimum zero).
                costPerUnit:
                    type: integer
                    minimum: 0
                    description: Overage price per unit in the smallest unit of `currency`.
                overageCost:
                    type: integer
                    minimum: 0
                    description: Overage total in the smallest unit of `currency` (overage multiplied by costPerUnit).
                callCount:
                    type: integer
                    minimum: 0
                    description: Number of calls (only present for callMinutes and forwardingMinutes).
                messageCount:
                    type: integer
                    minimum: 0
                    description: Number of messages (only present for sms and whatsapp).

        BillingUsage:
            type: object
            additionalProperties: false
            required:
                - callMinutes
                - forwardingMinutes
                - sms
                - whatsapp
                - additionalAssistants
            properties:
                callMinutes:
                    allOf:
                        - $ref: "#/components/schemas/BillingUsageEntry"
                        - description: Inbound call minutes handled by assistants.
                forwardingMinutes:
                    allOf:
                        - $ref: "#/components/schemas/BillingUsageEntry"
                        - description: Call forwarding minutes.
                sms:
                    allOf:
                        - $ref: "#/components/schemas/BillingUsageEntry"
                        - description: SMS segments sent.
                whatsapp:
                    allOf:
                        - $ref: "#/components/schemas/BillingUsageEntry"
                        - description: WhatsApp messages sent.
                additionalAssistants:
                    allOf:
                        - $ref: "#/components/schemas/BillingUsageEntry"
                        - description: Additional assistants beyond the plan's included count.

        BillingSummaryTotals:
            type: object
            additionalProperties: false
            required:
                - totalOverageCost
                - currency
            properties:
                totalOverageCost:
                    type: integer
                    minimum: 0
                    description: Sum of all service overage costs in the smallest unit of `currency`.
                currency:
                    type: string
                    example: EUR

        OrganizationBillingSummaryResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - organization
                - window
                - plan
                - usage
                - totals
            properties:
                status:
                    type: string
                    enum:
                        - success
                organization:
                    $ref: "#/components/schemas/BillingSummaryOrganizationRef"
                window:
                    $ref: "#/components/schemas/UsagePeriod"
                plan:
                    $ref: "#/components/schemas/DistributorPlanSummary"
                usage:
                    $ref: "#/components/schemas/BillingUsage"
                totals:
                    $ref: "#/components/schemas/BillingSummaryTotals"
            example:
                status: success
                organization:
                    id: "69c175970f3e28310926a1aa"
                    name: "Acme GmbH"
                    externalCustomerId: "SYN-4711"
                    externalContractId: "CON-2026-0001"
                window:
                    from: "2026-03-01"
                    to: "2026-03-31"
                plan:
                    id: "6967b1c7138bed3e7f6bf7e6"
                    slug: "advanced"
                    name: "Advanced"
                    commitment: "monthly"
                    currency: "EUR"
                    baseFee: 6900
                usage:
                    callMinutes:
                        included: 500
                        used: 623
                        overage: 123
                        costPerUnit: 15
                        overageCost: 1845
                        callCount: 247
                    forwardingMinutes:
                        included: 100
                        used: 45
                        overage: 0
                        costPerUnit: 25
                        overageCost: 0
                        callCount: 18
                    sms:
                        included: 200
                        used: 312
                        overage: 112
                        costPerUnit: 10
                        overageCost: 1120
                        messageCount: 298
                    whatsapp:
                        included: 100
                        used: 0
                        overage: 0
                        costPerUnit: 5
                        overageCost: 0
                        messageCount: 0
                    additionalAssistants:
                        included: 3
                        used: 5
                        overage: 2
                        costPerUnit: 990
                        overageCost: 1980
                totals:
                    totalOverageCost: 4945
                    currency: "EUR"

        BillingSummaryOrganizationRef:
            type: object
            additionalProperties: false
            required:
                - id
                - name
                - externalCustomerId
                - externalContractId
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                name:
                    type: string
                externalCustomerId:
                    type: string
                externalContractId:
                    type: string
                    nullable: true

        OrganizationUsageResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - organization
                - period
                - usage
            properties:
                status:
                    type: string
                    enum:
                        - success
                organization:
                    $ref: "#/components/schemas/UsageOrganizationRef"
                period:
                    $ref: "#/components/schemas/UsageDateTimePeriod"
                usage:
                    $ref: "#/components/schemas/UsageMetrics"

        PartnerUsageOrganization:
            type: object
            additionalProperties: false
            required:
                - id
                - name
                - externalCustomerId
                - usage
            properties:
                id:
                    type: string
                    pattern: "^[a-fA-F0-9]{24}$"
                name:
                    type: string
                externalCustomerId:
                    type: string
                usage:
                    $ref: "#/components/schemas/UsageMetrics"

        PartnerUsageResponse:
            type: object
            additionalProperties: false
            required:
                - status
                - period
                - totals
                - organizationCount
                - organizations
            properties:
                status:
                    type: string
                    enum:
                        - success
                period:
                    $ref: "#/components/schemas/UsagePeriod"
                totals:
                    $ref: "#/components/schemas/UsageMetrics"
                organizationCount:
                    type: integer
                    minimum: 0
                organizations:
                    type: array
                    items:
                        $ref: "#/components/schemas/PartnerUsageOrganization"
