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

# Send an upload's file

> The `url` createUpload answered: send the file as the body, once (`curl -sS -X PUT --data-binary @<file> <url>`); the content type is ignored, and no key is needed, as the URL authorizes it. An items file is stored only if every line is valid JSON and a valid example (blank lines are skipped): otherwise 400 UPLOAD_INVALID, with the first issues in `details.issues`, each message naming its line (from 1) and item, and `param` the line's field. A send has 15 minutes to arrive, and no bytes for 60 seconds cuts it off: 400 BODY_INVALID, its message saying whether the same URL takes the file again. A team has at most 10 sends under way at once: past that, 429 RATE_LIMITED with `details.limit` `upload_concurrency` and Retry-After. A structure file that doesn't validate is 400 CONTENT_INVALID, with every issue. A file over its limit is 413 UPLOAD_TOO_LARGE. Nothing is kept from a refused file, and the URL takes another try until it expires. 404 UPLOAD_UNKNOWN for an unknown, expired or mistyped URL; 409 UPLOAD_RECEIVED once its file is stored, with the receipt in `details.receipt`, and UPLOAD_BUSY while another send of it is under way. A body shorter than its Content-Length is 400 BODY_INVALID. 403 ORG_CLOSED for a closed team. 200 with what was stored: for items, `lines` and `labeled`; for a structure, its `structure_hash`; for both, `bytes` and the file's `sha256`.



## OpenAPI

````yaml /openapi.json put /v1/uploads/{upload_id}/{token}
openapi: 3.1.0
info:
  title: Moonbounce Decisions API
  version: 0.0.0
  description: >-
    Moonbounce decisions: evaluate an item's state against a decision structure
    (System One questions, attributes you supply and aggregations over both),
    record feedback and import labeled examples, review a ranked queue, and
    improve the structure through suggestions that ship as code diffs, each with
    a preview (backtest) and an evidence label.


    **Authentication.** Send an API key as `Authorization: Bearer <key>` on
    every operation except `GET /health` and `POST /v1/validate`, and an
    upload's own `url` (`PUT /v1/uploads/{upload_id}/{token}`), which its token
    authorizes. Call the API from a server, never a browser: a key must not
    reach one, and the API sends no CORS headers. A key belongs to one
    organization and one environment: production, or test for a key with the
    `env:test` permission. Test traffic never counts as production traffic.


    **Requests.** JSON bodies (`Content-Type: application/json`) of at most 1
    MB, parsed strictly: duplicate keys, lone surrogates, integers outside
    ±(2^53 - 1), `__proto__` keys, U+0000 and nesting deeper than 64 are a 400.
    A larger dataset, or a file you'd rather not paste, goes up as a file:
    `createUpload` answers a URL to PUT it to.


    **Retries.** Send an `Idempotency-Key` on every POST under /v1 that takes
    one (all but `POST /v1/validate`, `POST /v1/review/items/fill`, whose retry
    is another fill within the day's budget, and `createUpload`, whose retry is
    another upload). Keep it for the request's retries. The same key with the
    same request returns the first stored result, marked `Idempotent-Replayed:
    true`, for 24 hours (410 if that result was since deleted); with another
    request it is 422 IDEMPOTENCY_KEY_REUSED. Keys are scoped to the
    organization and environment across every operation, so use a fresh key per
    request. A request that failed before its result was stored runs again when
    retried. 429 and 503 under /v1 may carry `Retry-After` (seconds); a daily
    QUOTA_EXCEEDED cap instead names its reset time.


    **Lists.** A list answers one page: `{environment, items, next_cursor,
    has_more}` (named `<Thing>Page`), in the order the operation states. Pass
    `next_cursor` back as `cursor`, with the same filters, while `has_more` is
    true; on the last page `next_cursor` is null. `limit` defaults to 50 and is
    at most 100, with these exceptions: cases go up to 200 a page (a run's
    batch; 50 with content), suggestions default to 20, since each carries its
    whole candidate structure and evidence, and the review queue (a reviewer's
    screen) defaults to 20, at most 50.


    **Errors.** Every error under /v1 is `{"error": {"type", "message",
    "reason"?, "param"?, "details"?}}` (ErrorBody); `GET /health` answers its
    own shape. Every response carries a `Request-Id` header. Rate limits apply
    per key, and per client IP for requests without a valid key; a 429
    RATE_LIMITED carries `Retry-After`.
servers:
  - url: https://api.moonbounce.ai
    description: Production
security: []
tags:
  - name: Evaluate
    description: Run one item through a decision and read evaluations back. Start here.
  - name: Decisions
    description: >-
      Validate a decision file, register its versions, and read decisions and
      versions.
  - name: Feedback
    description: >-
      Say what the right answer was, import labeled examples, and read feedback
      and cases.
  - name: Suggestions
    description: Ask for a better version of a decision, and follow the improvement loop.
  - name: Uploads
    description: >-
      Send a dataset or structure file once, then name it in runs, imports and
      registrations.
  - name: Previews & tests
    description: >-
      Replay a structure change over past cases, and score a version over many
      items.
  - name: Models
    description: List the models a decision can run on and choose one.
  - name: Objective
    description: >-
      Set what a decision optimizes for, then judge runs and tune cuts against
      it.
  - name: Usage
    description: What a decision's model calls cost.
  - name: Account
    description: Identify your key, and check that the API is up.
paths:
  /v1/uploads/{upload_id}/{token}:
    put:
      tags:
        - Uploads
      summary: Send an upload's file
      description: >-
        The `url` createUpload answered: send the file as the body, once (`curl
        -sS -X PUT --data-binary @<file> <url>`); the content type is ignored,
        and no key is needed, as the URL authorizes it. An items file is stored
        only if every line is valid JSON and a valid example (blank lines are
        skipped): otherwise 400 UPLOAD_INVALID, with the first issues in
        `details.issues`, each message naming its line (from 1) and item, and
        `param` the line's field. A send has 15 minutes to arrive, and no bytes
        for 60 seconds cuts it off: 400 BODY_INVALID, its message saying whether
        the same URL takes the file again. A team has at most 10 sends under way
        at once: past that, 429 RATE_LIMITED with `details.limit`
        `upload_concurrency` and Retry-After. A structure file that doesn't
        validate is 400 CONTENT_INVALID, with every issue. A file over its limit
        is 413 UPLOAD_TOO_LARGE. Nothing is kept from a refused file, and the
        URL takes another try until it expires. 404 UPLOAD_UNKNOWN for an
        unknown, expired or mistyped URL; 409 UPLOAD_RECEIVED once its file is
        stored, with the receipt in `details.receipt`, and UPLOAD_BUSY while
        another send of it is under way. A body shorter than its Content-Length
        is 400 BODY_INVALID. 403 ORG_CLOSED for a closed team. 200 with what was
        stored: for items, `lines` and `labeled`; for a structure, its
        `structure_hash`; for both, `bytes` and the file's `sha256`.
      operationId: putUpload
      parameters:
        - schema:
            $ref: '#/components/schemas/UploadId'
          required: true
          name: upload_id
          in: path
        - schema:
            type: string
            pattern: ^[A-Za-z0-9_-]{43}$
            description: The URL's token.
          required: true
          description: The URL's token.
          name: token
          in: path
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: The file was stored
          content:
            application/json:
              examples:
                dataset:
                  summary: >-
                    A dataset file of 240 examples, 200 of them labeled with an
                    outcome
                  value:
                    environment: production
                    upload_id: upl_01900000-0000-7000-8000-000000000001
                    kind: items
                    lines: 240
                    labeled: 200
                    bytes: 412870
                    sha256: >-
                      3f1a0c6b5e2d4f8a9b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a
                    expires_at: '2026-11-10T13:00:00Z'
                structure:
                  summary: >-
                    The refund structure, named by its hash in dry runs and
                    registration
                  value:
                    environment: production
                    upload_id: upl_01900000-0000-7000-8000-000000000001
                    kind: structure
                    structure_hash: >-
                      sha256:bdad6e5b1b879209305f1555b43ec62666a4d74a925280c66e54cc4dc07bb1ae
                    question_hashes:
                      item_used: >-
                        sha256:2cd936709034818c3beca2574ca48ef400577728a20532924ed68f1ca1f26e3d
                      defective: >-
                        sha256:8dd4a69d6bfaf9fa766adcac7cfa1c4af7eff57a690a1e39666398bf85cd1869
                    warnings: []
                    bytes: 2048
                    sha256: >-
                      9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d
                    expires_at: '2026-11-10T13:00:00Z'
              schema:
                $ref: '#/components/schemas/UploadReceipt'
        '400':
          description: 'Invalid request: `param` names the field at fault when one applies'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '403':
          description: The upload's team is closed (reason ORG_CLOSED)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '404':
          description: >-
            No upload takes a file at this URL: it is unknown, expired or
            mistyped (reason UPLOAD_UNKNOWN)
          x-error-reasons:
            - UPLOAD_UNKNOWN
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '409':
          description: >-
            UPLOAD_RECEIVED: the file is stored, its receipt in
            `details.receipt`; UPLOAD_BUSY: another send of it is under way
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '413':
          description: >-
            The file is over its limit: 50 MB or 50,000 lines for items, 1 MB
            for a structure (reason UPLOAD_TOO_LARGE)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '429':
          description: >-
            RATE_LIMITED: too many requests from this address, or
            (`details.limit` `upload_concurrency`) the team has 10 sends under
            way; retry after Retry-After seconds
          headers:
            Retry-After:
              schema:
                type: string
                description: Seconds to wait before retrying, when waiting helps.
              required: false
              description: Seconds to wait before retrying, when waiting helps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '503':
          description: >-
            Temporarily unavailable, with Retry-After (reason
            STORE_UNAVAILABLE): send the file to the same URL again; 409
            UPLOAD_RECEIVED then means this send was stored, its receipt in
            `details.receipt`
          headers:
            Retry-After:
              schema:
                type: string
                description: Seconds to wait before retrying, when waiting helps.
              required: true
              description: Seconds to wait before retrying, when waiting helps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
      security: []
components:
  schemas:
    UploadId:
      type: string
      pattern: >-
        ^upl_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
    UploadReceipt:
      oneOf:
        - $ref: '#/components/schemas/ItemsUploadReceipt'
        - $ref: '#/components/schemas/StructureUploadReceipt'
      discriminator:
        propertyName: kind
        mapping:
          items: '#/components/schemas/ItemsUploadReceipt'
          structure: '#/components/schemas/StructureUploadReceipt'
      description: What the upload stored.
    ErrorBody:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              $ref: '#/components/schemas/ErrorType'
            reason:
              type: string
              pattern: ^[A-Z][A-Z0-9_]+$
              description: >-
                Set where a caller can act on it, one of the reasons in the
                ErrorReason schema. New reasons may be added: treat an unknown
                one by the status and `type`.
            message:
              type: string
              description: For people; don't parse it.
            param:
              type: string
              description: >-
                The request field at fault, as a dotted path
                (`attribute_values.region`, `claims.1.value`).
            details:
              type: object
              properties:
                issues:
                  type: array
                  items:
                    $ref: '#/components/schemas/ErrorIssue'
              additionalProperties: {}
          required:
            - type
            - message
          additionalProperties: true
          x-moonbounce-sdk-known-fields: true
      required:
        - error
      additionalProperties: true
      x-moonbounce-sdk-known-fields: true
    ItemsUploadReceipt:
      type: object
      properties:
        environment:
          $ref: '#/components/schemas/Environment'
        upload_id:
          $ref: '#/components/schemas/UploadIdOutput'
        bytes:
          type: integer
          description: The file's size.
        sha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: >-
            The SHA-256 of the file as received, in hex: compare it with your
            file's (`shasum -a 256 <file>`).
        expires_at:
          type: string
          format: date-time
          description: >-
            How long calls can name the upload: about a day. Upload the file
            again after that.
        kind:
          type: string
          enum:
            - items
        lines:
          type: integer
          description: >-
            Examples stored, one per non-blank line, in file order (offset 0
            first).
        labeled:
          type: integer
          description: >-
            Lines with an expected_outcome claim, which a run carries as
            `expected`.
      required:
        - environment
        - upload_id
        - bytes
        - sha256
        - expires_at
        - kind
        - lines
        - labeled
      additionalProperties: true
      x-moonbounce-sdk-known-fields: true
    StructureUploadReceipt:
      type: object
      properties:
        environment:
          $ref: '#/components/schemas/Environment'
        upload_id:
          $ref: '#/components/schemas/UploadIdOutput'
        bytes:
          type: integer
          description: The file's size.
        sha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: >-
            The SHA-256 of the file as received, in hex: compare it with your
            file's (`shasum -a 256 <file>`).
        expires_at:
          type: string
          format: date-time
          description: >-
            How long calls can name the upload: about a day. Upload the file
            again after that.
        kind:
          type: string
          enum:
            - structure
        structure_hash:
          type: string
          pattern: ^sha256:[0-9a-f]{64}$
          description: >-
            Name it in a dry run (`structure_hash`) or register it (`POST
            /v1/decisions` with `{decision_id, structure_hash}`) while the
            upload lives.
        question_hashes:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/QuestionHash'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/ValidateWarning'
          description: >-
            What `POST /v1/validate` warns of: valid, but probably not meant
            (`UNUSED_FEATURE`).
      required:
        - environment
        - upload_id
        - bytes
        - sha256
        - expires_at
        - kind
        - structure_hash
        - question_hashes
        - warnings
      additionalProperties: true
      x-moonbounce-sdk-known-fields: true
    ErrorType:
      type: string
      enum:
        - invalid_request
        - authentication
        - permission_denied
        - not_found
        - conflict
        - rate_limited
        - engine_failed
        - unavailable
        - internal
    ErrorIssue:
      type: object
      properties:
        param:
          type: string
        code:
          type: string
          pattern: ^[A-Z][A-Z0-9_]+$
          description: >-
            Stable, extensible issue code; unknown codes still have a parameter
            and human-readable message.
        message:
          type: string
      required:
        - param
        - code
        - message
      additionalProperties: true
      x-moonbounce-sdk-known-fields: true
    Environment:
      type: string
      enum:
        - production
        - test
      description: >-
        The environment of the key that made the call: `production` or `test`.
        Evaluations, feedback, reviews and suggestions are separate per
        environment; decisions and their versions belong to the whole
        organization.
    UploadIdOutput:
      type: string
      pattern: >-
        ^upl_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
    QuestionHash:
      type: string
      pattern: ^sha256:[0-9a-f]{64}$
    ValidateWarning:
      type: object
      properties:
        code:
          type: string
          pattern: ^[A-Z][A-Z0-9_]+$
          description: >-
            Stable, extensible warning code. `UNUSED_FEATURE`: the result never
            reaches this question, attribute or aggregation, so it changes the
            hash but never the outcome.
        param:
          type: string
          description: >-
            The field the warning is about, such as
            `structure.questions.item_used`.
        message:
          type: string
      required:
        - code
        - param
        - message
      additionalProperties: true
      x-moonbounce-sdk-known-fields: true

````

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