openapi: 3.0.3
info:
  title: Fuel API
  description: |
    Decision Science Corp Fuel v2 — JSON API (full parity with UI is the long-term goal).
    Multihost-safe: every path is a real `.php` file (no rewrite rules). Authenticated JSON endpoints accept **X-API-Key** / **Bearer** or the same **session cookie** as the admin UI unless noted.
  version: 0.3.2
servers:
  - url: https://fuel.decisionsciencecorp.com
    description: Production
  - url: http://127.0.0.1:8080
    description: Local (php -S)

tags:
  - name: meta
    description: Service metadata
  - name: session
    description: Browser session (human operators)
  - name: api-keys
    description: Bearer / X-API-Key integrations
  - name: users
    description: User and API key management (admin/manager)
  - name: attachments
    description: File attachments on tasks and deliverables
  - name: webhooks
    description: Outbound webhook targets and delivery log (admin/manager)
  - name: audit
    description: Audit log (admin/manager)
  - name: accounting
    description: |
      Job types and per-user / per-client rates (admin/manager). Rates are USD per 1,000 words for every kind, including transcription:
      word-based billing matches Whisper-style token costs, not per audio minute.

paths:
  /api/v1/health.php:
    get:
      tags: [meta]
      summary: Health check
      operationId: health
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'

  /api/v1/openapi.php:
    get:
      tags: [meta]
      summary: OpenAPI document (YAML)
      operationId: getOpenApi
      responses:
        '200':
          description: OpenAPI 3 YAML
          content:
            application/yaml:
              schema:
                type: string

  /api/v1/me.php:
    get:
      tags: [api-keys]
      summary: Current principal (API key)
      operationId: apiMe
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Authenticated user for this key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /admin/login.php:
    get:
      tags: [session]
      summary: Login form (HTML)
      operationId: loginForm
      responses:
        '200':
          description: HTML
    post:
      tags: [session]
      summary: Submit credentials
      operationId: loginSubmit
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
                csrf_token:
                  type: string
      responses:
        '302':
          description: Redirect to admin on success
        '401':
          description: Invalid credentials

  /admin/logout.php:
    post:
      tags: [session]
      summary: End session
      operationId: logout
      responses:
        '302':
          description: Redirect to login

  /api/v1/session-me.php:
    get:
      tags: [session]
      summary: Current user (browser session cookie)
      operationId: sessionMe
      responses:
        '200':
          description: OK
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/list-clients.php:
    get:
      tags: [api-keys]
      summary: List clients (scoped by role)
      operationId: listClients
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/create-client.php:
    post:
      tags: [api-keys]
      summary: Create client (admin/manager only)
      operationId: createClient
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Forbidden

  /api/v1/list-statuses.php:
    get:
      tags: [api-keys]
      summary: Task status definitions
      operationId: listStatuses
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-tasks.php:
    get:
      tags: [api-keys]
      summary: List tasks
      operationId: listTasks
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/get-task.php:
    get:
      tags: [api-keys]
      summary: Get one task
      operationId: getTask
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK
        '404':
          description: Not found

  /api/v1/create-task.php:
    post:
      tags: [api-keys]
      summary: Create task
      operationId: createTask
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/update-task.php:
    post:
      tags: [api-keys]
      summary: Update task
      operationId: updateTask
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/delete-task.php:
    post:
      tags: [api-keys]
      summary: Delete task
      operationId: deleteTask
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-comments.php:
    get:
      tags: [api-keys]
      summary: List comments for task
      operationId: listComments
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/create-comment.php:
    post:
      tags: [api-keys]
      summary: Add task comment
      operationId: createComment
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/list-deliverables.php:
    get:
      tags: [api-keys]
      summary: List deliverables
      operationId: listDeliverables
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/get-deliverable.php:
    get:
      tags: [api-keys]
      summary: Get one deliverable
      operationId: getDeliverable
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/create-deliverable.php:
    post:
      tags: [api-keys]
      summary: Create deliverable
      operationId: createDeliverable
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/update-deliverable.php:
    post:
      tags: [api-keys]
      summary: Update deliverable
      operationId: updateDeliverable
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-job-types.php:
    get:
      tags: [accounting]
      summary: List job types (billing categories)
      operationId: listJobTypes
      security:
        - ApiKeyAuth: []
      parameters:
        - name: active_only
          in: query
          required: false
          schema: { type: string, enum: ['0', '1'] }
          description: If 1, only active types
      responses:
        '200':
          description: OK
        '403':
          description: Forbidden

  /api/v1/save-job-type.php:
    post:
      tags: [accounting]
      summary: Create or update a job type (omit id to create)
      operationId: saveJobType
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Updated
        '201':
          description: Created
        '403':
          description: Forbidden

  /api/v1/get-user-pay-rates.php:
    get:
      tags: [accounting]
      summary: Pay rates for a user (all job types, admin/manager)
      operationId: getUserPayRates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: user_id
          in: query
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: OK
        '403':
          description: Forbidden
        '404':
          description: Not found

  /api/v1/save-user-pay-rates.php:
    post:
      tags: [accounting]
      summary: Set pay rates (USD per 1k words per job type)
      operationId: saveUserPayRates
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK
        '403':
          description: Forbidden

  /api/v1/get-client-bill-rates.php:
    get:
      tags: [accounting]
      summary: Bill rates for a client (all job types)
      operationId: getClientBillRates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: client_id
          in: query
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: OK
        '403':
          description: Forbidden
        '404':
          description: Not found

  /api/v1/save-client-bill-rates.php:
    post:
      tags: [accounting]
      summary: Set client bill rates (USD per 1k words per job type)
      operationId: saveClientBillRates
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK
        '403':
          description: Forbidden

  /api/v1/list-users.php:
    get:
      tags: [users]
      summary: List users (admin/manager)
      operationId: listUsers
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK
        '403':
          description: Forbidden

  /api/v1/get-user.php:
    get:
      tags: [users]
      summary: Get one user
      operationId: getUser
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: query
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: OK
        '404':
          description: Not found

  /api/v1/create-user.php:
    post:
      tags: [users]
      summary: Create user (admin/manager)
      operationId: createUser
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/update-user.php:
    post:
      tags: [users]
      summary: Update user (admin/manager)
      operationId: updateUser
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-api-keys.php:
    get:
      tags: [users]
      summary: List API keys for a user
      operationId: listApiKeys
      security:
        - ApiKeyAuth: []
      parameters:
        - name: user_id
          in: query
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: OK

  /api/v1/create-api-key.php:
    post:
      tags: [users]
      summary: Create API key (returns plaintext once)
      operationId: createApiKey
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/revoke-api-key.php:
    post:
      tags: [users]
      summary: Revoke API key
      operationId: revokeApiKey
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-audit-logs.php:
    get:
      tags: [audit]
      summary: Audit log (admin/manager)
      operationId: listAuditLogs
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-attachments.php:
    get:
      tags: [attachments]
      summary: List attachments for task or deliverable
      operationId: listAttachments
      security:
        - ApiKeyAuth: []
      parameters:
        - name: scope
          in: query
          required: true
          schema: { type: string, enum: [task, deliverable] }
        - name: scope_id
          in: query
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: OK

  /api/v1/upload-attachment.php:
    post:
      tags: [attachments]
      summary: Multipart upload (field name file; scope, scope_id as form fields)
      operationId: uploadAttachment
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/delete-attachment.php:
    post:
      tags: [attachments]
      summary: Delete attachment by id
      operationId: deleteAttachment
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/download-attachment.php:
    get:
      tags: [attachments]
      summary: Download file (session cookie or API key)
      operationId: downloadAttachment
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: query
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Binary stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  /api/v1/list-webhook-targets.php:
    get:
      tags: [webhooks]
      summary: List webhook targets (no secrets)
      operationId: listWebhookTargets
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/create-webhook-target.php:
    post:
      tags: [webhooks]
      summary: Create webhook target
      operationId: createWebhookTarget
      security:
        - ApiKeyAuth: []
      responses:
        '201':
          description: Created

  /api/v1/update-webhook-target.php:
    post:
      tags: [webhooks]
      summary: Update webhook target
      operationId: updateWebhookTarget
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/list-webhook-deliveries.php:
    get:
      tags: [webhooks]
      summary: Webhook delivery log
      operationId: listWebhookDeliveries
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

  /api/v1/replay-webhook-delivery.php:
    post:
      tags: [webhooks]
      summary: Queue replay delivery
      operationId: replayWebhookDelivery
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'

  schemas:
    HealthResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            service:
              type: string
              example: fuel
            version:
              type: string
              example: "0.3.2"
            database:
              type: string
              enum: [ok, error]

    MeResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            user:
              type: object
              properties:
                user_id:
                  type: integer
                username:
                  type: string
                role:
                  type: string
                client_id:
                  type: integer
                  nullable: true

    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
        error_object:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            details:
              nullable: true
