> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outerlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Emit an artifact (evidence for a pull request)

> Accepts one artifact — a screenshot, recording, report, or log emitted as proof that a change works — plus its content-addressed blob. The artifact anchors to a pull request directly (prNumber), through the recorded session that produced it, or through git context resolved to a PR later; a request with no anchor at all is refused and nothing is stored. `kind` is inferred from the media type and provenance (session / ci / local) is derived from the submission path — neither can be supplied by the caller. A session binding must name a session that already synced for the app, with `turnIndex` within its recorded turns; note the binding proves the session exists, not that the bytes came from it. `ci` is advisory: it is honored only for a shared (non-actor) API key and downgrades to `local` otherwise. An XML file whose root is `testsuites` or `testsuite` is stored as kind `test` with its parsed tests; `tests` selects which of them bind to the criterion. A JUnit file that declares a DOCTYPE, malformed XML bound to a criterion, or a selected test the file lacks is refused with `unreadable_test_results`. `bytes` must equal the decoded blob length. `emittedAt` is display metadata; because it also starts the unmatched grace clock, the stored value is clamped to a bounded window ending at server receipt time. Retrying with the same clientArtifactId returns the already-stored artifact when the content matches and fails with 409 when it does not. `replaces` names the clientArtifactId of an earlier artifact this one corrects: on success that artifact is marked superseded in the same request, and every display surface hides it from then on (its bytes and deep link still work). `replacesTargets` names several at once (2-20 ids, no repeats) — one correction retires every listed target. REQUIRED together: when `replacesTargets` is present, `replaces` MUST also be present and equal `replacesTargets[0]` — a request with `replacesTargets` and no `replaces`, or a `replaces` that disagrees with the array's first element, is refused as malformed rather than guessed at, so a caller unaware of `replacesTargets` always sees exactly one target retired, never zero. Every named target is resolved by its own id and stamped in a single all-or-nothing operation: an unknown, cross-app, or already-superseded target refuses the WHOLE request, naming the offending id — nothing is created and nothing is stamped.



## OpenAPI

````yaml /openapi.yaml post /v1/artifacts
openapi: 3.0.3
info:
  contact:
    email: hello@outerlayer.ai
    url: https://www.outerlayer.ai
  description: >-
    The OuterLayer Gateway API lets you ingest coding-agent sessions and record
    evidence of work programmatically.


    Most teams reach it through the `outerlayer` CLI, which calls it for them.

    Call it directly to build your own integration or to automate what the
    dashboard does.


    Versioning: every endpoint is prefixed with `/v1/`. Breaking changes ship
    under a new version prefix (`/v2/`, etc.) with a 90-day-minimum deprecation
    window; anything additive lands on the current prefix.


    Vocabulary: the dashboard calls this API's `app` a **factory** — the unit
    that owns a set of repositories, the agent sessions worked on them, and the
    evidence they produce. They are the same entity. The API keeps `app` in its
    paths, headers, and field names because those are a published contract; only
    the product surface was renamed.
  title: OuterLayer Gateway API
  version: '1.0'
servers:
  - description: Production (OuterLayer Cloud)
    url: https://api.outerlayer.ai
  - description: Local dev server (self-hosted gateway)
    url: http://localhost:9418
security:
  - AppId: []
    BearerAuth: []
tags:
  - description: >-
      What a CLI login token can read about its owner without naming a factory,
      such as the factories they can use.
    name: Account
  - description: >-
      Coding-agent session ingest (outerlayer sync) and content-addressed
      session images.
    name: Agents
  - description: >-
      Evidence artifacts (screenshots, recordings, reports, logs) emitted as
      proof of a change and anchored to pull requests.
    name: Artifacts
  - description: >-
      Pass/fail outcomes of checks run in your own CI or compute, anchored to
      pull requests with the run URL as proof.
    name: Emitted Results
  - description: >-
      The acceptance criteria of a work item, recorded as one list. The Criteria
      tab, its count and the item status read that list.
    name: Criteria
  - description: >-
      What any agent found wrong about a change under review or about a rule it
      ran on, recorded on a work item.
    name: Findings
  - description: >-
      Add or remove a source's statement that it is working on an issue or pull
      request, and read what is currently live.
    name: Work Items
  - description: >-
      What the factory's GitHub App installation lets builds do on each
      connected repository, and whether each default branch requires a pull
      request.
    name: Repositories
  - description: >-
      A runner host's heartbeat — its CLI version, slots and last request for
      work — and the factory's list of hosts with a derived status.
    name: Hosts
  - description: Query server feature availability.
    name: Capabilities
  - description: Per-model LLM pricing data. Public, unauthenticated.
    name: Pricing
  - description: >-
      Create, list, and revoke factory API keys. Plaintext returned exactly once
      at creation.
    name: API Keys
  - description: >-
      Factory CRUD — a factory is owned by an organization and is the unit every
      other resource hangs off. Lets a headless agent provision one without the
      dashboard.
    name: Apps
  - description: >-
      Agent-coding session list and full transcript reads, with actor-privacy
      controls for machine keys.
    name: Sessions
  - description: >-
      Per-model token spend and fleet-wide agent behavior tiles, including
      two-window comparisons.
    name: Metrics
  - description: Synced-commit history of the app's `.outerlayer/` context tree.
    name: Context
  - description: >-
      Session→PR attribution: which agent sessions produced which pull requests,
      and what each attributed PR cost.
    name: PRs
  - description: Service health checks.
    name: Health
  - description: >-
      Org member and role administration, authenticated with an org-scoped
      management API key (`olk_…`) minted in the dashboard's settings.
    name: Org Management
  - description: OAuth 2.1 discovery metadata for MCP connector clients.
    name: OAuth
paths:
  /v1/artifacts:
    post:
      tags:
        - Artifacts
      summary: Emit an artifact (evidence for a pull request)
      description: >-
        Accepts one artifact — a screenshot, recording, report, or log emitted
        as proof that a change works — plus its content-addressed blob. The
        artifact anchors to a pull request directly (prNumber), through the
        recorded session that produced it, or through git context resolved to a
        PR later; a request with no anchor at all is refused and nothing is
        stored. `kind` is inferred from the media type and provenance (session /
        ci / local) is derived from the submission path — neither can be
        supplied by the caller. A session binding must name a session that
        already synced for the app, with `turnIndex` within its recorded turns;
        note the binding proves the session exists, not that the bytes came from
        it. `ci` is advisory: it is honored only for a shared (non-actor) API
        key and downgrades to `local` otherwise. An XML file whose root is
        `testsuites` or `testsuite` is stored as kind `test` with its parsed
        tests; `tests` selects which of them bind to the criterion. A JUnit file
        that declares a DOCTYPE, malformed XML bound to a criterion, or a
        selected test the file lacks is refused with `unreadable_test_results`.
        `bytes` must equal the decoded blob length. `emittedAt` is display
        metadata; because it also starts the unmatched grace clock, the stored
        value is clamped to a bounded window ending at server receipt time.
        Retrying with the same clientArtifactId returns the already-stored
        artifact when the content matches and fails with 409 when it does not.
        `replaces` names the clientArtifactId of an earlier artifact this one
        corrects: on success that artifact is marked superseded in the same
        request, and every display surface hides it from then on (its bytes and
        deep link still work). `replacesTargets` names several at once (2-20
        ids, no repeats) — one correction retires every listed target. REQUIRED
        together: when `replacesTargets` is present, `replaces` MUST also be
        present and equal `replacesTargets[0]` — a request with
        `replacesTargets` and no `replaces`, or a `replaces` that disagrees with
        the array's first element, is refused as malformed rather than guessed
        at, so a caller unaware of `replacesTargets` always sees exactly one
        target retired, never zero. Every named target is resolved by its own id
        and stamped in a single all-or-nothing operation: an unknown, cross-app,
        or already-superseded target refuses the WHOLE request, naming the
        offending id — nothing is created and nothing is stamped.
      operationId: emit-artifact
      requestBody:
        content:
          application/json:
            schema:
              properties:
                artifact:
                  properties:
                    bytes:
                      minimum: 0
                      type: integer
                    caption:
                      maxLength: 500
                      type: string
                    ci:
                      type: boolean
                    clientArtifactId:
                      pattern: ^[A-Za-z0-9_.:-]{1,64}$
                      type: string
                    commitSha:
                      maxLength: 64
                      type: string
                    criterionId:
                      pattern: ^[A-Za-z0-9_.:-]{1,64}$
                      type: string
                    emittedAt:
                      type: string
                    filename:
                      maxLength: 120
                      minLength: 1
                      type: string
                    gitBranch:
                      maxLength: 255
                      type: string
                    gitRepo:
                      maxLength: 300
                      type: string
                    mediaType:
                      maxLength: 100
                      minLength: 1
                      type: string
                    prNumber:
                      exclusiveMinimum: true
                      minimum: 0
                      type: integer
                    replaces:
                      pattern: ^[A-Za-z0-9_.:-]{1,64}$
                      type: string
                    replacesTargets:
                      items:
                        pattern: ^[A-Za-z0-9_.:-]{1,64}$
                        type: string
                      maxItems: 20
                      minItems: 2
                      type: array
                      uniqueItems: true
                    repository:
                      maxLength: 200
                      type: string
                    session:
                      properties:
                        sessionId:
                          maxLength: 128
                          minLength: 1
                          type: string
                        turnIndex:
                          minimum: 0
                          type: integer
                      required:
                        - sessionId
                      type: object
                    sha256:
                      pattern: ^[0-9a-f]{64}$
                      type: string
                    tests:
                      items:
                        properties:
                          file:
                            type: string
                          line:
                            exclusiveMinimum: true
                            maximum: 10000000
                            minimum: 0
                            type: integer
                          name:
                            maxLength: 500
                            minLength: 1
                            type: string
                        required:
                          - name
                        type: object
                      maxItems: 100
                      minItems: 1
                      type: array
                  required:
                    - clientArtifactId
                    - filename
                    - mediaType
                    - bytes
                    - sha256
                    - caption
                    - emittedAt
                  type: object
                blob:
                  properties:
                    data:
                      minLength: 1
                      type: string
                  required:
                    - data
                  type: object
                schemaVersion:
                  enum:
                    - 1
                  type: number
              required:
                - schemaVersion
                - artifact
                - blob
              type: object
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    properties:
                      id:
                        type: string
                      kind:
                        enum:
                          - video
                          - screenshot
                          - report
                          - log
                          - test
                          - file
                        type: string
                      prNumber:
                        nullable: true
                        type: integer
                      provenance:
                        enum:
                          - session
                          - ci
                          - local
                        type: string
                      repository:
                        type: string
                      verification:
                        enum:
                          - pending
                          - confirmed
                          - unmatched
                        type: string
                    required:
                      - id
                      - kind
                      - provenance
                      - verification
                      - prNumber
                      - repository
                    type: object
                required:
                  - data
                type: object
          description: The stored artifact (the existing row on an idempotent retry).
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                    type: object
                required:
                  - error
                type: object
          description: >-
            Malformed body, sha256 or bytes mismatch, unknown session binding,
            turn index out of range, unknown/cross-app/already-superseded
            replaces target, or nothing to attach to.
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                    type: object
                required:
                  - error
                type: object
          description: Missing or invalid API key.
        '409':
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                    type: object
                required:
                  - error
                type: object
          description: The clientArtifactId is already stored with different content.
        '413':
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type: string
                      limit:
                        type: integer
                      message:
                        type: string
                    required:
                      - code
                      - message
                      - limit
                    type: object
                required:
                  - error
                type: object
          description: Blob or request exceeds the byte ceiling (`error.limit` names it).
        '429':
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type: string
                      currentBytes:
                        type: number
                      limitBytes:
                        type: number
                      message:
                        type: string
                    required:
                      - code
                      - message
                      - currentBytes
                      - limitBytes
                    type: object
                required:
                  - error
                type: object
          description: >-
            Monthly storage cap exceeded (`error.currentBytes` /
            `error.limitBytes` carry the usage).
        '503':
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                      - code
                      - message
                    type: object
                required:
                  - error
                type: object
          description: Ingest temporarily unavailable; retry after the interval.
          headers:
            Retry-After:
              required: false
              schema:
                minimum: 0
                type: integer
components:
  securitySchemes:
    AppId:
      description: Factory id the request is scoped to.
      in: header
      name: X-Outerlayer-App-Id
      type: apiKey
    BearerAuth:
      description: API key (sk_outerlayer_*)
      scheme: bearer
      type: http

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.