openapi: 3.1.0
info:
  title: Recursift Customer Query API
  version: 0.1.0
servers:
  - url: https://api.recursift.app/v1
security:
  - customerKey: []
paths:
  /me:
    get:
      operationId: getCaller
      responses:
        '200': {description: "Calling key ID, customer ID and scopes"}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /agents:
    get:
      operationId: listAgents
      description: Requires agents:read. Returns allowlisted metadata only.
      parameters:
        - {$ref: '#/components/parameters/Cursor'}
        - {$ref: '#/components/parameters/Limit'}
      responses:
        '200': {description: "agents array and next_cursor"}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /queries:
    post:
      operationId: createQuery
      description: Requires query:write. Every explicit target must belong to this customer.
      parameters:
        - name: Idempotency-Key
          in: header
          schema: {type: string, maxLength: 128}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/Ask'}
      responses:
        '202': {description: "Asynchronous receipt with id, status, agent_total and deadline; retrieve findings with GET /queries/{id}"}
        '400': {description: "Invalid request"}
        '404': {description: "One or more targets are unavailable"}
        '409': {description: "Idempotency key reused for a different request"}
        '429': {description: "Customer, endpoint or key limit reached; Retry-After header is seconds"}
    get:
      operationId: listQueries
      description: Requires query:read.
      parameters:
        - {$ref: '#/components/parameters/Cursor'}
        - {$ref: '#/components/parameters/Limit'}
      responses:
        '200': {description: "queries array and next_cursor"}
  /queries/{id}:
    parameters:
      - {$ref: '#/components/parameters/QueryID'}
    get:
      operationId: getQuery
      description: Requires query:read. Materializes findings and finalizes expired queries.
      responses:
        '200': {description: "Query with counts, findings and unanswered_agents"}
        '404': {description: "Query unavailable to this customer"}
  /queries/{id}/cancel:
    parameters:
      - {$ref: '#/components/parameters/QueryID'}
    post:
      operationId: cancelQuery
      description: Requires query:write. Stops pending delivery and rejects late answers; does not interrupt an already running model.
      responses:
        '200': {description: "Receipt with id, status, agent_total and deadline; terminal queries are unchanged"}
        '404': {description: "Query unavailable to this customer"}
  /findings:
    get:
      operationId: searchFindings
      description: Requires query:read. Substring search of materialized answer summaries, not exhaustive IOC coverage.
      parameters:
        - {$ref: '#/components/parameters/Cursor'}
        - {$ref: '#/components/parameters/Limit'}
        - name: search
          in: query
          schema: {type: string, maxLength: 200}
      responses:
        '200': {description: "findings array, coverage description and next_cursor"}
  /audit/events:
    get:
      operationId: listAuditEvents
      description: Requires audit:read. Cursor is the previous sequence number.
      parameters:
        - {$ref: '#/components/parameters/Cursor'}
        - {$ref: '#/components/parameters/Limit'}
      responses:
        '200': {description: "events array containing seq, event_json, prev_hash, hash; next_cursor"}
components:
  securitySchemes:
    customerKey:
      type: http
      scheme: bearer
      bearerFormat: rsk_live customer API key
  parameters:
    Cursor:
      name: cursor
      in: query
      schema: {type: string, maxLength: 200}
    Limit:
      name: limit
      in: query
      schema: {type: integer, minimum: 1, maximum: 100, default: 50}
    QueryID:
      name: id
      in: path
      required: true
      schema: {type: string}
  responses:
    Unauthorized:
      description: Missing, invalid, expired or revoked key
  schemas:
    Ask:
      type: object
      additionalProperties: false
      required: [question, target]
      properties:
        question: {type: string, minLength: 1, maxLength: 500}
        timeout_seconds: {type: integer, minimum: 5, maximum: 120, default: 120}
        target:
          type: object
          additionalProperties: false
          required: [agent_ids]
          properties:
            agent_ids:
              type: array
              minItems: 1
              maxItems: 100
              uniqueItems: true
              items: {type: string}
