openapi: 3.1.0
info:
  title: Babco Labs Control Plane API
  version: 0.3.0-permanent
  description: Application onboarding, scoped AI usage ingestion, OpenAI project discovery, and official cost reconciliation.
servers:
  - url: https://babco-labs-control-9cb297fb.azurewebsites.net
paths:
  /api/health:
    get:
      summary: Safe service diagnostics
      responses:
        '200': { description: Service state }
  /api/v1/me:
    get:
      summary: Current Microsoft Entra identity and Control Plane role
      responses:
        '200': { description: Identity state }
  /api/v1/connection-requests:
    post:
      summary: Request an automatic application connection
      description: The application proves possession of its RSA private key. No existing Control Plane credential is required.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ConnectionRequestInput' }
      responses:
        '202': { description: New request accepted and awaiting administrator approval }
        '200': { description: Existing idempotent request returned }
        '400': { description: Invalid request or public key }
        '401': { description: Invalid proof of private-key possession }
        '409': { description: Conflicting request for the same application identity }
  /api/v1/connection-requests/{requestId}:
    get:
      summary: Poll connection approval and retrieve the encrypted credential envelope
      parameters:
        - in: path
          name: requestId
          required: true
          schema: { type: string, format: uuid }
        - in: header
          name: X-Babco-Connection-Token
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Current request state. Approved responses include an encrypted credential envelope.
        '401': { description: Invalid temporary connection token }
        '404': { description: Request not found }
  /api/v1/admin/connection-requests:
    get:
      summary: List pending and completed application connection requests
      description: Requires a signed-in Control Plane administrator.
      parameters:
        - in: query
          name: status
          schema: { type: string, enum: [pending, approved, rejected, expired] }
      responses:
        '200': { description: Connection requests and pending count }
        '401': { description: Administrator access required }
  /api/v1/admin/connection-requests/{requestId}/approve:
    post:
      summary: Approve a request against an exact registered application/OpenAI project
      description: Creates an additional scoped credential, encrypts it to the requester public key, and does not revoke existing credentials.
      parameters:
        - in: path
          name: requestId
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [applicationId]
              properties:
                applicationId: { type: string, format: uuid }
      responses:
        '200': { description: Approved and encrypted envelope prepared }
        '401': { description: Administrator access required }
        '404': { description: Request or application not found }
        '409': { description: Provider, project, environment, or state mismatch }
  /api/v1/admin/connection-requests/{requestId}/reject:
    post:
      summary: Reject an application connection request
      parameters:
        - in: path
          name: requestId
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string, maxLength: 500 }
      responses:
        '200': { description: Request rejected }
        '401': { description: Administrator access required }
        '404': { description: Request not found }
        '409': { description: Request is no longer pending }
  /api/v1/usage-events:
    post:
      summary: Record one application model-usage event
      security:
        - ApplicationId: []
          ApplicationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UsageEvent' }
      responses:
        '202': { description: Accepted }
        '400': { description: Invalid event }
        '401': { description: Invalid application credential }
  /api/v1/applications/me/summary:
    get:
      summary: Retrieve the authenticated application's reconciled summary
      security:
        - ApplicationId: []
          ApplicationKey: []
      parameters:
        - { in: query, name: from, schema: { type: string, format: date-time } }
        - { in: query, name: to, schema: { type: string, format: date-time } }
      responses:
        '200': { description: Application summary }
        '401': { description: Invalid application credential }
  /api/v1/admin/summary:
    get:
      summary: Organization-wide project reconciliation summary
      description: Requires a signed-in Babco reader/admin or emergency admin key.
      responses:
        '200': { description: Organization summary }
  /api/v1/admin/diagnostics:
    get:
      summary: Safe operational diagnostics
      responses:
        '200': { description: Diagnostic state }
  /api/v1/admin/openai/sync:
    post:
      summary: Trigger official OpenAI usage and cost synchronization
      security: [{ AdminKey: [] }]
      responses:
        '200': { description: Completed sync run }
  /api/v1/admin/openai/projects/import:
    post:
      summary: Discover OpenAI organization projects and create or update Control Plane mappings
      security: [{ AdminKey: [] }]
      responses:
        '200': { description: Import result }
  /api/v1/admin/bootstrap:
    post:
      summary: Import projects, issue missing legacy credentials, and run initial reconciliation
      security: [{ AdminKey: [] }]
      responses:
        '200': { description: Permanent bootstrap result }
components:
  securitySchemes:
    AdminKey:
      type: apiKey
      in: header
      name: X-Babco-Admin-Key
      description: Emergency/automation administrator credential. Human access normally uses Microsoft Entra sign-in.
    ApplicationId:
      type: apiKey
      in: header
      name: X-Babco-Application-Id
    ApplicationKey:
      type: apiKey
      in: header
      name: X-Babco-Control-Key
  schemas:
    ConnectionRequestInput:
      type: object
      required:
        - protocolVersion
        - displayName
        - environment
        - instanceId
        - publicKeyPem
        - pollToken
        - proof
      properties:
        protocolVersion: { type: string, const: '1' }
        displayName: { type: string, maxLength: 200 }
        slug: { type: string, maxLength: 100, pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$' }
        environment: { type: string, enum: [development, staging, production, shared] }
        requestedBy: { type: string, maxLength: 200 }
        ownerContact: { type: string, maxLength: 300 }
        provider: { type: string, const: openai, default: openai }
        providerProjectId: { type: string, maxLength: 200 }
        providerProjectName: { type: string, maxLength: 200 }
        instanceId: { type: string, maxLength: 200 }
        publicKeyPem:
          type: string
          description: RSA SPKI PEM public key, minimum 2048 bits.
        pollToken:
          type: string
          minLength: 32
          description: Temporary cryptographically random token. Only its SHA-256 hash is stored.
        hosting:
          type: object
          additionalProperties:
            oneOf:
              - { type: string }
              - { type: number }
              - { type: boolean }
        proof:
          type: string
          description: Base64url RSA-SHA256 signature over the canonical request without the proof field.
    CredentialEnvelope:
      type: object
      properties:
        version: { type: string, const: '1' }
        algorithm: { type: string, const: RSA-OAEP-256+A256GCM }
        encryptedKey: { type: string }
        iv: { type: string }
        tag: { type: string }
        ciphertext: { type: string }
    UsageEvent:
      type: object
      required: [model, inputTokens, outputTokens]
      properties:
        occurredAt: { type: string, format: date-time }
        provider: { type: string, default: openai }
        model: { type: string }
        requestId: { type: string }
        providerApiKeyId: { type: string }
        userId: { type: string }
        feature: { type: string }
        inputTokens: { type: integer, minimum: 0 }
        cachedInputTokens: { type: integer, minimum: 0, default: 0 }
        outputTokens: { type: integer, minimum: 0 }
        requestCount: { type: integer, minimum: 1, default: 1 }
        estimatedCostUsd: { type: number, minimum: 0, default: 0 }
        pricingVersion: { type: string }
        status: { type: string, enum: [succeeded, failed, cancelled], default: succeeded }
        metadata: { type: object, additionalProperties: true }
