openapi: 3.0.3
info:
  title: Partner API
  version: '1.0'
  description: "API for approved partners to manage child organisations and retrieve usage statistics. Partner authentication uses an approved organisation API key in the `x-api-key` header. To enable developer logs for public-api requests, add the `x-developer-logs: true` header."
servers:
  - url: https://api.gospace.app/partner/v1
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
  schemas:
    PartnerOrganisation:
      type: object
      properties:
        organisation_id:
          type: string
          description: MongoDB identifier for the organisation.
          example: 65f1a7a8260c3f3322a0e1b9
        organisation_key:
          type: string
          description: gospace organisation key used for tenant context.
          example: prod_123456789abc
        organisation_name:
          type: string
          example: Example Child Organisation
        country:
          type: string
          minLength: 2
          maxLength: 2
          example: GB
        division_key:
          type: string
          description: Initial division key created with the organisation.
          example: 1c3906b5-d770-4d82-9d0c-c22f1c12e6aa
        deleted_at:
          type: string
          format: date-time
          nullable: true
        partner_external_id:
          type: string
          description: Optional partner-side identifier for this organisation.
          example: customer-12345
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - organisation_id
        - organisation_key
        - organisation_name
        - country
        - created_at
        - updated_at
    PartnerOrganisationWithApiKey:
      allOf:
        - $ref: '#/components/schemas/PartnerOrganisation'
        - type: object
          properties:
            organisation_api_key:
              type: string
              description: Newly generated standard organisation API key. Returned only when the organisation is created.
              example: prod_123456789abc.gb.1c3906b5-d770-4d82-9d0c-c22f1c12e6aa.4f9f2d80-0de8-478f-a55d-2f3376fe7a8b
          required:
            - organisation_api_key
    CreatePartnerOrganisationRequest:
      type: object
      properties:
        organisation_name:
          type: string
          minLength: 1
          example: Example Child Organisation
        country:
          type: string
          minLength: 2
          maxLength: 2
          description: ISO 3166-1 alpha-2 country code.
          example: GB
        division_timezone:
          type: string
          minLength: 1
          example: Europe/London
        division_name:
          type: string
          minLength: 1
          description: Optional name for the initial division. Defaults to `organisation_name`.
          example: Headquarters
        partner_external_id:
          type: string
          minLength: 1
          description: Optional partner-side identifier for this organisation.
          example: customer-12345
      required:
        - organisation_name
        - country
        - division_timezone
    UpdatePartnerOrganisationRequest:
      type: object
      additionalProperties: false
      properties:
        organisation_name:
          type: string
          minLength: 1
        partner_external_id:
          type: string
          minLength: 1
      minProperties: 1
    PartnerUsage:
      type: object
      properties:
        organisation_id:
          type: string
        organisation_key:
          type: string
        usage_month:
          type: string
          pattern: '^\\d{4}-\\d{2}$'
          example: 2026-04
        timezone:
          type: string
          example: UTC
        total_bytes:
          type: number
          default: 0
        total_documents:
          type: number
          default: 0
        days_recorded:
          type: number
          default: 0
        peak_total_bytes:
          type: number
          default: 0
        peak_total_documents:
          type: number
          default: 0
        total_requests:
          type: number
          default: 0
        api_days_recorded:
          type: number
          default: 0
        peak_total_requests:
          type: number
          default: 0
      required:
        - organisation_id
        - organisation_key
        - usage_month
        - timezone
        - total_bytes
        - total_documents
        - days_recorded
        - peak_total_bytes
        - peak_total_documents
        - total_requests
        - api_days_recorded
        - peak_total_requests
    DeletePartnerOrganisationResult:
      type: object
      properties:
        organisation_ids:
          type: array
          description: Organisation IDs that were soft-deleted.
          items:
            type: string
      required:
        - organisation_ids
    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: false
        identifier:
          type: string
        error:
          type: object
          additionalProperties: true
        violation:
          type: object
          additionalProperties: true
      required:
        - success
        - identifier
  responses:
    InternalServerError:
      description: Internal Server Error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            success: false
            identifier: internal_server_error
            error:
              code: 500
              message: internal server error
    PartnerOrganisationNotFound:
      description: Organisation not found or not owned by the authenticated partner.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            success: false
            identifier: partner_organisation_not_found
            violation:
              message: organisation not found
    Unauthorised:
      description: Missing, malformed, disabled, deleted, or non-partner API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            success: false
            identifier: unauthorised
            error:
              code: 401
              message: unauthorised
security:
  - ApiKeyAuth: []
paths:
  /organisations:
    post:
      operationId: createPartnerOrganisation
      description: Create a child organisation owned by the authenticated partner. The response includes one newly generated standard organisation API key. Use that child organisation API key for data writes into the child organisation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePartnerOrganisationRequest'
            example:
              organisation_name: Example Child Organisation
              country: GB
              division_timezone: Europe/London
              division_name: Headquarters
              partner_external_id: customer-12345
      responses:
        '200':
          description: Organisation created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  identifier:
                    type: string
                    enum:
                      - partner_organisation_created
                  data:
                    $ref: '#/components/schemas/PartnerOrganisationWithApiKey'
                required:
                  - success
                  - identifier
                  - data
              example:
                success: true
                identifier: partner_organisation_created
                data:
                  organisation_id: 65f1a7a8260c3f3322a0e1b9
                  organisation_key: prod_123456789abc
                  organisation_name: Example Child Organisation
                  country: GB
                  division_key: 1c3906b5-d770-4d82-9d0c-c22f1c12e6aa
                  deleted_at: null
                  partner_external_id: customer-12345
                  created_at: 2026-04-22T09:00:00.000Z
                  updated_at: 2026-04-22T09:00:00.000Z
                  organisation_api_key: prod_123456789abc.gb.1c3906b5-d770-4d82-9d0c-c22f1c12e6aa.4f9f2d80-0de8-478f-a55d-2f3376fe7a8b
        '400':
          description: Bad Request - Invalid request body.
        '401':
          $ref: '#/components/responses/Unauthorised'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listPartnerOrganisations
      description: List child organisations owned by the authenticated partner. Deleted organisations are excluded by default.
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: skip
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: include_deleted
          in: query
          required: false
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Organisations listed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  identifier:
                    type: string
                    enum:
                      - partner_organisations_listed
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PartnerOrganisation'
                required:
                  - success
                  - identifier
                  - data
        '401':
          $ref: '#/components/responses/Unauthorised'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /organisations/{organisation_id}:
    get:
      operationId: getPartnerOrganisation
      description: Retrieve one child organisation by MongoDB organisation ID. Organisations not owned by the authenticated partner are rejected.
      parameters:
        - name: organisation_id
          in: path
          required: true
          schema:
            type: string
            pattern: '^[a-f\\d]{24}$'
        - name: include_deleted
          in: query
          required: false
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Organisation found.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  identifier:
                    type: string
                    enum:
                      - partner_organisation_found
                  data:
                    $ref: '#/components/schemas/PartnerOrganisation'
                required:
                  - success
                  - identifier
                  - data
        '401':
          $ref: '#/components/responses/Unauthorised'
        '404':
          $ref: '#/components/responses/PartnerOrganisationNotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    patch:
      operationId: updatePartnerOrganisation
      description: Update safe partner-facing organisation metadata only. Fields such as `country`, `location`, `shard_key`, `organisation_key`, `divisions`, `is_partner`, and ownership fields cannot be updated through this API.
      parameters:
        - name: organisation_id
          in: path
          required: true
          schema:
            type: string
            pattern: '^[a-f\\d]{24}$'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePartnerOrganisationRequest'
            example:
              organisation_name: Updated Child Organisation
              partner_external_id: customer-12345-updated
      responses:
        '200':
          description: Organisation updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  identifier:
                    type: string
                    enum:
                      - partner_organisation_updated
                  data:
                    $ref: '#/components/schemas/PartnerOrganisation'
                required:
                  - success
                  - identifier
                  - data
        '400':
          description: Bad Request - Invalid request body.
        '401':
          $ref: '#/components/responses/Unauthorised'
        '404':
          $ref: '#/components/responses/PartnerOrganisationNotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deletePartnerOrganisation
      description: Soft-delete an owned child organisation and revoke its organisation API keys. If the deleted target is itself a partner organisation, child organisations linked to that partner are also soft-deleted and their keys revoked.
      parameters:
        - name: organisation_id
          in: path
          required: true
          schema:
            type: string
            pattern: '^[a-f\\d]{24}$'
      responses:
        '200':
          description: Organisation deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  identifier:
                    type: string
                    enum:
                      - partner_organisation_deleted
                  data:
                    $ref: '#/components/schemas/DeletePartnerOrganisationResult'
                required:
                  - success
                  - identifier
                  - data
        '401':
          $ref: '#/components/responses/Unauthorised'
        '404':
          $ref: '#/components/responses/PartnerOrganisationNotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /organisations/{organisation_id}/usage:
    get:
      operationId: getPartnerOrganisationUsage
      description: Retrieve storage and API request usage for an owned child organisation for a single month. Monthly totals are returned when available; otherwise daily storage and API usage records are aggregated.
      parameters:
        - name: organisation_id
          in: path
          required: true
          schema:
            type: string
            pattern: '^[a-f\\d]{24}$'
        - name: usage_month
          in: query
          required: true
          schema:
            type: string
            pattern: '^\\d{4}-\\d{2}$'
            example: 2026-04
      responses:
        '200':
          description: Usage found.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  identifier:
                    type: string
                    enum:
                      - partner_organisation_usage_found
                  data:
                    $ref: '#/components/schemas/PartnerUsage'
                required:
                  - success
                  - identifier
                  - data
        '400':
          description: Bad Request - Invalid usage month.
        '401':
          $ref: '#/components/responses/Unauthorised'
        '404':
          $ref: '#/components/responses/PartnerOrganisationNotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
