openapi: 3.0.3
info:
  title: SOAT Policies API
  version: 1.0.0
  description: API for managing global policies (admin-only)
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: Policies
    description: Manage policies
security:
  - bearerAuth: []
paths:
  /api/v1/policies:
    get:
      tags:
        - Policies
      summary: List all policies
      description: >
        Returns global policies. Requires admin role. Pass user_id to list only
        the policies attached to that user (replaces the former per-user
        policies endpoint).
      operationId: listPolicies
      parameters:
        - name: user_id
          in: query
          required: false
          description: Return only policies attached to this user (user_...)
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of policies
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PolicyRecord'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden (non-admin user)
    post:
      tags:
        - Policies
      summary: Create a policy
      description: Creates a new global policy. Requires admin role.
      operationId: createPolicy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - document
              properties:
                name:
                  type: string
                  example: ReadOnlyAccess
                description:
                  type: string
                  example: Allows read-only access to all resources
                document:
                  $ref: '#/components/schemas/PolicyDocument'
      responses:
        '201':
          description: Policy created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyRecord'
        '400':
          description: Bad request (invalid policy document)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden (non-admin user)
  /api/v1/policies/{policy_id}:
    get:
      tags:
        - Policies
      summary: Get a policy
      description: Returns details of a specific policy. Requires admin role.
      operationId: getPolicy
      parameters:
        - name: policy_id
          in: path
          required: true
          description: Policy public ID (pol_ prefix)
          schema:
            type: string
            example: pol_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Policy details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyRecord'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden (non-admin user)
        '404':
          description: Policy not found
    put:
      tags:
        - Policies
      summary: Update a policy
      description: Updates an existing global policy. Requires admin role.
      operationId: updatePolicy
      parameters:
        - name: policy_id
          in: path
          required: true
          description: Policy public ID (pol_ prefix)
          schema:
            type: string
            example: pol_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - document
              properties:
                name:
                  type: string
                description:
                  type: string
                document:
                  $ref: '#/components/schemas/PolicyDocument'
      responses:
        '200':
          description: Policy updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyRecord'
        '400':
          description: Bad request (invalid policy document)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden (non-admin user)
        '404':
          description: Policy not found
    delete:
      tags:
        - Policies
      summary: Delete a policy
      description: Deletes a global policy. Requires admin role.
      operationId: deletePolicy
      parameters:
        - name: policy_id
          in: path
          required: true
          description: Policy public ID (pol_ prefix)
          schema:
            type: string
            example: pol_V1StGXR8Z5jdHi6B
      responses:
        '204':
          description: Policy deleted successfully
        '401':
          description: Unauthorized
        '403':
          description: Forbidden (non-admin user)
        '404':
          description: Policy not found
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    PolicyStatement:
      type: object
      required:
        - effect
        - action
      properties:
        effect:
          type: string
          enum: [Allow, Deny]
          example: Allow
        action:
          type: array
          items:
            type: string
          example: ['files:ListFiles', 'files:CreateFile']
        resource:
          type: array
          items:
            type: string
          example: ['srn:proj_abc:files:*']
        condition:
          type: object
          additionalProperties: true
          description: >
            Optional condition block. Keys are condition operators (e.g.
            StringEquals) mapping to context-key/value maps; evaluated by the
            policy compiler. Free-form — keys are dynamic, not validated.
            Operator and context keys are matched by exact string and are never
            case-converted, so they round-trip exactly as written:
            `soat:ResourceTag/cost_center` selects the tag `cost_center`, not
            `costCenter`.
          example: { StringEquals: { 'soat:ResourceTag/env': 'prod' } }
    PolicyDocument:
      type: object
      required:
        - statement
      properties:
        statement:
          type: array
          items:
            $ref: '#/components/schemas/PolicyStatement'
    PolicyRecord:
      type: object
      properties:
        id:
          type: string
          description: Public policy ID (pol_ prefix)
          example: pol_V1StGXR8Z5jdHi6B
        name:
          type: string
          nullable: true
          example: ReadOnlyAccess
        description:
          type: string
          nullable: true
        document:
          $ref: '#/components/schemas/PolicyDocument'
        created_at:
          type: string
          format: date-time
          example: '2024-01-01T00:00:00.000Z'
        updated_at:
          type: string
          format: date-time
          example: '2024-01-01T00:00:00.000Z'
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: >-
            Structured error. Every error response uses this shape — 401, 403
            and the 500 catch-all included — so `code` can be read without
            first checking the type of `error`.
          required:
            - code
            - message
            - hint
            - docs_url
          properties:
            code:
              type: string
              description: A key from the server's ERROR_CODES registry.
              example: RESOURCE_NOT_FOUND
            message:
              type: string
              example: 'Resource not found'
            hint:
              type: string
              description: >-
                What to do about this error. Resolved per code, so a caller that
                has never seen the code before can act on the response without
                leaving it.
              example: >-
                Check the id, and check that the credential can see the project
                that owns the resource.
            docs_url:
              type: string
              format: uri
              description: The reference-page anchor documenting this code.
              example: >-
                https://soat.ttoss.dev/docs/error-codes#resource_not_found
            meta:
              type: object
              description: Optional structured context for the error.
