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

# Mint an upload slot for a reply

> Step 1 of answering an information request: mint a **presigned upload
slot** for one file. Requires the **`cases:write`** scope, and the
request must still be `pending`. Call it once per file.

`PUT` the file's bytes to `uploadUrl` before `expiresAt`, with
**exactly** the `Content-Type` and `Content-Length` you declared **and
the header `If-None-Match: *`** — all three are pinned into the
signature, and the upload is rejected otherwise:

```http
PUT {uploadUrl}
Content-Type: application/pdf
Content-Length: 348211
If-None-Match: *
```

The URL is create-only: once the object exists a second `PUT` returns
`412 Precondition Failed`, so an upload cannot be replaced once it has
landed. `expiresAt` also bounds step 2 — a slot not attached by then is
refused, and you mint a fresh one.

**Frayme does not inspect file content.** The declared `contentType` is
pinned into the signature and checked against the stored object's
header, but the bytes are never sniffed, validated, or scanned for
malware. Scan files you collect from end customers before uploading
them — an attached document is shown to analysts and may be read by an
AI workflow node.

Every slot you mint counts against the budget for good, even one you
never upload to or that expires: **30 per request, 100 per case** (a
single response attaches at most 10 documents).




## OpenAPI

````yaml /api-reference/openapi.yaml post /cases/{caseId}/rfi/{rfiId}/documents
openapi: 3.1.0
info:
  title: Frayme Case API
  version: 1.0.0
  description: >
    The partner-facing HTTP API for the Frayme fraud-analysis platform. Submit

    **cases** (KYC, KYB, or Transaction), poll their status, act on paused

    workflow nodes, and deliver external callbacks.


    All requests authenticate with a tenant-scoped API key sent in the

    `X-API-Key` header. Each key carries one or more **scopes** — the required

    scope is listed on every operation below.


    | Scope | Grants |

    | --- | --- |

    | `cases:write` | Submit new cases, and answer an information request |

    | `cases:read` | Read a case, list paused nodes, list durable actions |

    | `cases:callback` | Deliver an external callback, or invoke a pending /
    durable action |

    | `tags:read` | List a case's tags (`GET /cases/{caseId}/tags`) |

    | `tags:write` | Add or remove a case's tags (`POST` / `DELETE
    /cases/{caseId}/tags`) |


    Your tenant is derived from the API key — never send `tenantId` in a request

    body; it is ignored. The base URL and your webhook configuration are

    provisioned for you by Frayme.
servers:
  - url: https://core.us.api.frayme.io
    description: Production.
  - url: https://core.us.api.stg.frayme.io
    description: Staging.
security:
  - apiKeyAuth: []
tags:
  - name: Cases
    description: Submit and read cases.
  - name: Pending actions
    description: Act on a workflow node that is paused awaiting an external system.
  - name: Durable actions
    description: Invoke a broker action on a case after it has been decided.
  - name: Callbacks
    description: Deliver the result of an external callback back to a paused node.
  - name: Information requests
    description: Record a customer's reply to a request for information.
  - name: Tags
    description: Read and manage the labels applied to a case.
paths:
  /cases/{caseId}/rfi/{rfiId}/documents:
    post:
      tags:
        - Information requests
      summary: Mint an upload slot for a reply
      description: |
        Step 1 of answering an information request: mint a **presigned upload
        slot** for one file. Requires the **`cases:write`** scope, and the
        request must still be `pending`. Call it once per file.

        `PUT` the file's bytes to `uploadUrl` before `expiresAt`, with
        **exactly** the `Content-Type` and `Content-Length` you declared **and
        the header `If-None-Match: *`** — all three are pinned into the
        signature, and the upload is rejected otherwise:

        ```http
        PUT {uploadUrl}
        Content-Type: application/pdf
        Content-Length: 348211
        If-None-Match: *
        ```

        The URL is create-only: once the object exists a second `PUT` returns
        `412 Precondition Failed`, so an upload cannot be replaced once it has
        landed. `expiresAt` also bounds step 2 — a slot not attached by then is
        refused, and you mint a fresh one.

        **Frayme does not inspect file content.** The declared `contentType` is
        pinned into the signature and checked against the stored object's
        header, but the bytes are never sniffed, validated, or scanned for
        malware. Scan files you collect from end customers before uploading
        them — an attached document is shown to analysts and may be read by an
        AI workflow node.

        Every slot you mint counts against the budget for good, even one you
        never upload to or that expires: **30 per request, 100 per case** (a
        single response attaches at most 10 documents).
      operationId: mintRfiUploadSlot
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: '#/components/parameters/RfiId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MintUploadRequest'
            example:
              filename: proof_of_address.pdf
              contentType: application/pdf
              sizeBytes: 348211
      responses:
        '201':
          description: The slot was minted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSlot'
              example:
                uploadId: 9a2e4d71-0b83-4c17-8f5a-1d6e7c0b3a49
                uploadUrl: >-
                  https://example-documents-bucket.s3.amazonaws.com/tenants/tenant_01HABCXYZ/cases/case_01HABCXYZ/rfi-uploads/9a2e4d71-0b83-4c17-8f5a-1d6e7c0b3a49?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=EXAMPLE
                expiresAt: '2026-09-16T10:12:00Z'
        '400':
          description: >-
            `filename`, `contentType`, or a positive `sizeBytes` is missing;
            `filename` exceeds 255 bytes; `contentType` is outside
            `application/pdf`, `image/jpeg`, `image/png`; or `sizeBytes` is
            outside 1 byte – 20 MB.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: No API key was supplied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The API key is missing the `cases:write` scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such case or information request for this tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            The request can no longer take documents: `already_answered`,
            `not_pending`, `case_terminal`, or `dry_run`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RFIError'
        '422':
          description: >-
            `too_many_documents` — the per-request (30) or per-case (100) slot
            budget is exhausted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RFIError'
components:
  parameters:
    CaseId:
      name: caseId
      in: path
      required: true
      description: The case identifier returned by `POST /cases`.
      schema:
        type: string
    RfiId:
      name: rfiId
      in: path
      required: true
      description: >-
        The information request's id, delivered as `rfi_id` on the
        `case.rfi_requested` webhook.
      schema:
        type: string
        format: uuid
  schemas:
    MintUploadRequest:
      type: object
      required:
        - filename
        - contentType
        - sizeBytes
      properties:
        filename:
          type: string
          maxLength: 255
          description: The file's name, stored with the slot. Maximum 255 bytes.
        contentType:
          type: string
          enum:
            - application/pdf
            - image/jpeg
            - image/png
          description: Pinned into the presigned URL's signature.
        sizeBytes:
          type: integer
          format: int64
          minimum: 1
          maximum: 20971520
          description: The exact byte length, pinned into the signature. Maximum 20 MB.
    UploadSlot:
      type: object
      properties:
        uploadId:
          type: string
          format: uuid
          description: Reference this in `documents[].uploadId` when you submit the answer.
        uploadUrl:
          type: string
          format: uri
          description: The presigned `PUT` URL. Create-only for its whole lifetime.
        expiresAt:
          type: string
          format: date-time
          description: Bounds both the `PUT` and the attach in step 2.
    Error:
      type: object
      properties:
        error:
          type: string
          description: A human-readable error message.
    RFIError:
      type: object
      description: >-
        The error shape returned by the information-request response endpoints.
        Unlike `Error`, its `error` is a stable **machine-readable code** you
        branch on, and the remaining properties are the facts you need to
        recover — each present only on the codes described in the response
        above.
      properties:
        error:
          type: string
          description: >-
            The machine-readable code, for example `already_answered`,
            `not_pending`, `case_terminal`, `dry_run`, `too_many_documents`,
            `document_not_uploaded`, `invalid_field_key`, `invalid_field_value`,
            `fields_too_large`.
        answeredBy:
          type: string
          description: >-
            On `already_answered`, `apikey:<keyId>` when a key answered — so a
            retry after a lost response recognises its own answer — or the bare
            string `analyst` when an analyst recorded the reply in the console.
        answeredAt:
          type: string
          format: date-time
          description: On `already_answered`, when the request was answered.
        uploadId:
          type: string
          format: uuid
          description: On `document_not_uploaded`, the slot that failed.
        key:
          type: string
          description: On `invalid_field_key` / `invalid_field_value`, the offending key.
        message:
          type: string
          description: An optional human-readable detail.
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: A tenant-scoped API key provisioned by Frayme.

````