openapi: 3.1.0
info:
  title: Human In Circuit Review API
  version: 2.0.0
  description: Current Review API. New integrations use v2 review inboxes; v1 remains available for legacy workflow-key integrations.
servers:
  - url: https://humanincircuit.com
    description: Hosted Human In Circuit API
tags:
  - name: Reviews
    description: Create, retrieve, and cancel human reviews.
  - name: Attachments
    description: Prepare private files before attaching them to a review.
security:
  - bearerAuth: []
paths:
  /v2/reviews:
    post:
      tags: [Reviews]
      operationId: createReview
      summary: Create a review
      description: Creates a review in the single project that owns the API key.
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          description: Strongly recommended. Reuse a stable workflow-item value so retries return the existing review.
          schema:
            type: string
            maxLength: 255
          example: article-123-human-review
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateReview"
            examples:
              article:
                summary: Review an article and resume by callback
                value:
                  external_id: article-123
                  inbox_key: article-publishing
                  title: Approve generated article
                  summary: An article is ready for editorial review.
                  details_markdown: Check the claims, tone, and final wording.
                  agent:
                    name: Content agent
                    workflow: Publish article
                  metadata:
                    article_id: article-123
                    locale: en-IN
                  fields:
                    - key: article
                      label: Article
                      type: textarea
                      required: true
                  initial_values:
                    article: The generated article goes here.
                  callback:
                    url: https://n8n.example.com/webhook-waiting/example
      responses:
        "202":
          description: Review accepted or an existing idempotent review returned
          headers:
            X-HIC-API-Version:
              $ref: "#/components/headers/ApiVersion"
            X-HIC-Contract-Version:
              $ref: "#/components/headers/ContractVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateReviewResponse"
              example:
                review_id: d835e7dc-1f8f-4a75-a57b-0f2bd5826a1f
                api_version: v2
                schema_version: review.v1
                callback_version: callback.v1
                inbox_revision: 3
                project_key: content-publishing
                status: pending
                created_at: "2026-07-23T10:30:00.000Z"
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"
        "413":
          $ref: "#/components/responses/Error"
        "429":
          $ref: "#/components/responses/Error"
  /v2/reviews/{reviewId}:
    get:
      tags: [Reviews]
      operationId: getReview
      summary: Get review status and decision
      description: Poll this endpoint at any time. The stored result remains available after callback attempts finish.
      parameters:
        - $ref: "#/components/parameters/ReviewId"
      responses:
        "200":
          description: Current review state, with decision data after resolution
          content:
            application/json:
              schema:
                type: object
                required: [review]
                properties:
                  review:
                    $ref: "#/components/schemas/ReviewStatus"
        "401":
          $ref: "#/components/responses/Error"
        "404":
          $ref: "#/components/responses/Error"
  /v2/reviews/{reviewId}/cancel:
    post:
      tags: [Reviews]
      operationId: cancelReview
      summary: Cancel a pending review
      parameters:
        - $ref: "#/components/parameters/ReviewId"
      responses:
        "200":
          description: Review cancelled
          content:
            application/json:
              schema:
                type: object
                required: [status]
                properties:
                  status:
                    const: cancelled
        "401":
          $ref: "#/components/responses/Error"
        "409":
          $ref: "#/components/responses/Error"
  /v1/uploads:
    post:
      tags: [Attachments]
      operationId: createSignedUpload
      summary: Prepare a private attachment upload
      description: Request a short-lived signed upload URL, upload the file to it, then put the returned path in the review's attachments array. This endpoint is unnecessary for reviews without attachments.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename, content_type, size_bytes]
              properties:
                filename:
                  type: string
                  maxLength: 255
                content_type:
                  type: string
                  enum: [image/jpeg, image/png, image/webp, application/pdf, text/plain, text/markdown]
                size_bytes:
                  type: integer
                  minimum: 1
                  maximum: 10485760
      responses:
        "200":
          description: Signed upload information
          content:
            application/json:
              schema:
                type: object
                required: [path, token]
                properties:
                  path:
                    type: string
                    description: Use this as attachments[].storage_path.
                  token:
                    type: string
                  upload_url:
                    type: [string, "null"]
                    format: uri
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"
components:
  headers:
    ApiVersion:
      description: URL-major API contract selected by this API key.
      schema:
        type: string
        const: v2
    ContractVersion:
      description: Additive OpenAPI contract revision served by this deployment.
      schema:
        type: string
        const: 2.0.0
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key generated under Settings → API keys.
  parameters:
    ReviewId:
      in: path
      name: reviewId
      required: true
      description: The review_id returned when the review was created.
      schema:
        type: string
        format: uuid
  responses:
    Error:
      description: Request error
      content:
        application/json:
          schema:
            type: object
            required: [error]
            properties:
              error:
                type: string
  schemas:
    CreateReview:
      type: object
      required: [title, inbox_key]
      properties:
        schema_version:
          type: string
          const: review.v1
          default: review.v1
          description: Optional explicit review schema. Omit it to use the current v2 default.
        external_id:
          type: string
          maxLength: 255
          description: Stable ID of the originating record or workflow item.
        inbox_key:
          type: string
          maxLength: 64
          pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          description: Required project-scoped review inbox. Human In Circuit applies its routing and review behavior snapshot.
        title:
          type: string
          maxLength: 240
          description: Short action-oriented review title.
        summary:
          type: string
          maxLength: 2000
          description: Concise context visible on the review card.
        details_markdown:
          type: string
          maxLength: 100000
          description: Longer reviewer context in Markdown.
        priority:
          type: string
          enum: [low, normal, high, urgent]
          default: normal
          description: |
            Inbox and notification urgency:
            - `low`: non-urgent work that can wait behind normal reviews.
            - `normal`: default for everyday reviews.
            - `high`: time-sensitive or important work that should be seen sooner.
            - `urgent`: sorted first and sends an immediate email.
        risk:
          type: string
          enum: [low, medium, high]
          default: low
        expires_at:
          type: string
          format: date-time
        agent:
          $ref: "#/components/schemas/AgentMetadata"
        metadata:
          $ref: "#/components/schemas/Metadata"
        fields:
          type: array
          maxItems: 100
          default: []
          items:
            $ref: "#/components/schemas/ReviewField"
        initial_values:
          type: object
          default: {}
          additionalProperties: true
          description: Canonical starting values keyed by ReviewField.key. Human edits are returned in final_values and output.
        output:
          type: object
          additionalProperties: true
          description: Developer-friendly input alias for initial_values. Used when initial_values is omitted or empty.
        attachments:
          type: array
          maxItems: 5
          default: []
          items:
            $ref: "#/components/schemas/Attachment"
        callback:
          $ref: "#/components/schemas/Callback"
    AgentMetadata:
      type: object
      required: [name]
      properties:
        name:
          type: string
          maxLength: 120
        workflow:
          type: string
          maxLength: 200
        model:
          type: string
          maxLength: 200
          description: Optional model label. Omit it when it is not useful; the Inbox hides it when absent.
        prompt_version:
          type: string
          maxLength: 120
        tags:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 80
    ReviewField:
      type: object
      required: [key, label, type]
      properties:
        key:
          type: string
          description: Connects this definition to the same key in initial_values and final_values.
        label:
          type: string
        type:
          type: string
          enum: [text, textarea, number, boolean, date, time, datetime, url, email, select, multiselect, readonly]
        description:
          type: string
          maxLength: 500
          description: Helper text rendered directly below the field for the reviewer.
        required:
          type: boolean
          default: false
        placeholder:
          type: string
          maxLength: 160
          description: Optional hint for text and textarea fields.
        minLength:
          type: integer
          minimum: 0
          description: Minimum accepted length for text and textarea fields.
        maxLength:
          type: integer
          minimum: 1
          description: Maximum accepted length for text and textarea fields.
        min:
          type: number
          description: Minimum accepted number.
        max:
          type: number
          description: Maximum accepted number.
        step:
          type: number
          exclusiveMinimum: 0
          description: Step size for number inputs.
        options:
          type: array
          description: Required for select and multiselect fields.
          items:
            type: object
            required: [label, value]
            properties:
              label:
                type: string
              value: {}
    Metadata:
      type: object
      default: {}
      additionalProperties: true
      description: Caller-owned JSON, up to 16 KB, returned unchanged by create, polling, and review.resolved callbacks. It is not shown to reviewers.
    Attachment:
      type: object
      required: [name, storage_path, content_type, size_bytes]
      properties:
        name:
          type: string
        storage_path:
          type: string
          description: The path returned by POST /v1/uploads.
        content_type:
          type: string
        size_bytes:
          type: integer
          maximum: 10485760
    Callback:
      type: object
      required: [url]
      properties:
        version:
          type: string
          const: callback.v1
          default: callback.v1
          description: Callback payload schema pinned to this review.
        url:
          type: string
          format: uri
          description: Public HTTPS destination that receives review.resolved.
        auth_header:
          type: string
          maxLength: 120
          description: Optional fixed authentication header name.
        auth_value:
          type: string
          maxLength: 500
          description: Optional fixed authentication header value. Encrypted at rest.
    CreateReviewResponse:
      type: object
      required: [review_id, project_key, inbox_key, status, created_at, api_version, schema_version, callback_version, inbox_revision]
      properties:
        review_id:
          type: string
          format: uuid
          description: Unique identifier for this review.
        api_version:
          type: string
          const: v2
        schema_version:
          type: string
          const: review.v1
        callback_version:
          type: string
          const: callback.v1
        inbox_revision:
          type: [integer, "null"]
          minimum: 1
          description: Immutable review-inbox revision captured when the review was created.
        project_key:
          type: string
          description: Permanent readable identifier of the project that owns the API key and review.
        status:
          type: string
          enum: [pending]
        created_at:
          type: string
          format: date-time
        metadata:
          $ref: "#/components/schemas/Metadata"
        inbox_key:
          type: string
          description: Review inbox resolved inside the API key's project.
        idempotent_replay:
          type: boolean
          description: True when the same Idempotency-Key returned an existing review.
    ReviewStatus:
      type: object
      description: Stored review data. Decision fields are present after approval or rejection.
      properties:
        api_version:
          type: string
          const: v2
        schema_version:
          type: string
          const: review.v1
        callback_version:
          type: string
          const: callback.v1
        inbox_revision:
          type: [integer, "null"]
          minimum: 1
        policy_snapshot:
          type: object
          additionalProperties: true
          description: Immutable effective review-gate behavior captured at review creation.
        id:
          type: string
          format: uuid
        external_id:
          type: string
        metadata:
          $ref: "#/components/schemas/Metadata"
        status:
          type: string
          enum: [pending, decision_pending, approved, rejected, expired, cancelled]
        created_at:
          type: string
          format: date-time
        first_viewed_at:
          type: [string, "null"]
          format: date-time
        decided_at:
          type: [string, "null"]
          format: date-time
        projects:
          type: object
          properties:
            key:
              type: string
            name:
              type: string
        review_decisions:
          type: array
          items:
            type: object
            properties:
              outcome:
                type: string
                enum: [approved, rejected]
              final_values:
                type: object
                additionalProperties: true
              changes:
                type: array
                items:
                  type: object
              rejection_reasons:
                type: array
                items:
                  type: string
              note:
                type: string
