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

# Open a folder

> Open a folder container and get one upload URL per file inside it.

## Behavior

Upload each member's bytes to its `upload_url`, then finalize each member. The folder becomes `ready` when every member is finalized. A folder already open and ready with the same roster (member paths and declared sizes; contents are not compared) answers `200` with `reused: true` and nothing to upload; otherwise a new folder answers `201`. Files the instance will not read are listed in `skipped`. See [Attachments](/conversations/attachments#upload-a-folder).


## OpenAPI

````yaml POST /api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/attachments/folders
openapi: 3.1.0
info:
  title: Nexio API
  version: '1.0'
  description: |
    The Nexio public API. Submit runs to configured engines and read their
    results, hold conversations with configured assistants, make authorized
    reads over the planes and families of a connected system of record, and
    manage webhooks and environments.

    Six engine types are registered: comparison, matching, entity_analysis,
    diligence, triage, and opportunity.

    Every route under /api/v1/ requires `Authorization: Bearer <key>`, except
    inbound event ingest (`POST /api/v1/events/ingest/{source_key}`), which
    authenticates each request against its ingest source instead: an
    HMAC-SHA256 signature for `nexio` and `github` sources, or a shared
    authentication code for an `ams360_ons` source. Error
    bodies share the `Error` schema. Call the API from servers only: it sends
    no CORS headers.
  contact:
    email: support@usenexio.com
    url: https://docs.usenexio.com
servers:
  - url: https://api.usenexio.com
    description: >-
      Nexio API. Sandbox or live is chosen by the API key's environment, not by
      the host.
security:
  - BearerAuth: []
tags:
  - name: EngineManagement
    description: >-
      Create, configure, version and manage engines. An engine is a
      configuration of one registered engine type.
  - name: Engines
    description: Engine-scoped endpoints for submitting runs and retrieving results.
  - name: Runs
    description: Retrieve and reconcile submitted runs.
  - name: Records
    description: >-
      Typed, authorized reads over the registered planes and families of a
      connected system of record, and governed writes to the action ledger. The
      generic family read is the core contract.
  - name: Graph
    description: >-
      The organization's data graph: a read-only map of connections and the
      derivations scheduled on them. Node kinds are a closed registry of 16; the
      API serves 14.
  - name: Environments
    description: >-
      The live environment and up to 5 sandbox environments in your org.
      Organization keys only.
  - name: Webhooks
    description: >-
      Manage webhook endpoints that receive terminal run events (run.completed,
      run.failed, run.cancelled) and run corrections (run.superseded).
  - name: Events
    description: >-
      Send inbound events to your organization's event log through an ingest
      source. Source kinds are nexio, github and ams360_ons; a nexio source
      sends event types from your own vocabulary.
  - name: Converse
    description: The raw stateless conversational model turn (caller-owned state).
  - name: Conversations
    description: >-
      Conversation instances (orchestrators over engines) and their managed
      conversations, turns, annotations, evals, and exports.
  - name: Platform
    description: Service health. No key needed.
paths:
  /api/v1/conversation-instances/{instance_slug}/conversations/{conversation_id}/attachments/folders:
    parameters:
      - $ref: '#/components/parameters/InstanceSlug'
      - name: conversation_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - Conversations
      summary: Open a folder of attachments
      description: >
        Opens a folder container, a container your application opens, from

        its list of files, and returns one reservation (with its own

        `upload_url`) per file the instance will read. PUT each member's

        bytes to its `upload_url`, then finalize each member by its

        `attachment.id`. The folder is `ready` when every member is

        finalized, and a turn names the folder by `container.id`.


        At most 25 files per folder. Files the instance will not read are

        named in `skipped` with a reason, and the rest of the folder still

        opens. A folder with nothing readable is refused. A folder's identity

        is its roster: each member's path and declared size. File contents

        are not compared, so a file changed without changing its size is not

        detected. When a folder with the same roster is already open and

        `ready` in the conversation, the response is `200` with `reused: true`
        and nothing

        needs uploading; otherwise a new folder is opened with `201`. A

        folder counts as one attachment toward `max_files_per_message`, and

        the folder plus its files count toward the conversation's 100

        attachment limit. The folder record has `container_kind: folder`,

        `delivery: unwrap`, and status `unwrapping` until every member is

        finalized.


        Requires `conversations:use` for scoped keys. Scope is org,

        conversation, and end user; a miss on any of them is `404`.
      operationId: openConversationAttachmentFolder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - end_user
                - name
                - files
              properties:
                end_user:
                  type: string
                  description: >-
                    The asserted end-user identity the conversation must belong
                    to.
                name:
                  type: string
                  description: Folder name.
                files:
                  type: array
                  maxItems: 25
                  items:
                    type: object
                    additionalProperties: false
                    required:
                      - path
                      - size_bytes
                    properties:
                      path:
                        type: string
                        description: >-
                          The file's path inside the folder, as your application
                          holds it. Echoed back as `source_path`.
                      size_bytes:
                        type: integer
                        format: int64
                        description: >-
                          Declared size in bytes. Zero or less is skipped as
                          empty; over the per-file limit is skipped as too
                          large.
            example:
              end_user: u_dana_ortiz
              name: supplier-contracts
              files:
                - path: supplier-contracts/contract-2026.pdf
                  size_bytes: 482113
                - path: supplier-contracts/contract-2025.pdf
                  size_bytes: 391208
      responses:
        '200':
          description: >-
            The same folder was already open and ready; it is returned with
            `reused` true and no members to upload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConversationAttachmentFolder'
        '201':
          description: The opened folder and one reservation per readable file.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConversationAttachmentFolder'
        '400':
          description: |
            The instance slug is empty (`missing_instance_slug`), the body is
            unreadable, has an unknown field, or has no `name`
            (`invalid_request`), `end_user` is missing (`invalid_request`), the
            published config does not enable attachments
            (`attachments_not_enabled`), or the folder is refused for its shape
            (`attachment_rejected`): it has no files, more than 25 files, or
            the folder and its readable files would take the conversation past
            its limit of 100 attachments.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            A scoped key lacks the capability (`insufficient_capability`), or
            the instance is archived (`instance_archived`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            The instance (`instance_not_found`) or the conversation
            (`conversation_not_found`) is not in scope. A conversation in
            another organization, under another end user, or created with a key
            from another environment answers the same way.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: The instance has no published version (`instance_not_published`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '413':
          description: >-
            The readable files together exceed what one message can carry once
            base64-encoded, 52428800 bytes (`attachment_too_large`). A single
            file over the per-file limit is not refused here; it is listed in
            `skipped`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '415':
          description: >-
            No file in the folder is left to read once unreadable, oversized,
            empty, archive and unaccepted files are skipped
            (`attachment_rejected`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: >-
            The published config cannot be parsed (`instance_config_invalid`) or
            the folder could not be opened (`internal_error`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          $ref: '#/components/responses/AttachmentsUnavailable'
        '504':
          $ref: '#/components/responses/RequestTimeout'
      security:
        - BearerAuth: []
components:
  parameters:
    InstanceSlug:
      name: instance_slug
      in: path
      required: true
      description: Conversation instance identifier slug (e.g. `platform-assistant`).
      schema:
        type: string
  schemas:
    ConversationAttachmentFolder:
      type: object
      description: An opened folder container with one reservation per readable file.
      required:
        - container
        - reused
        - members
        - skipped
      properties:
        container:
          $ref: '#/components/schemas/ConversationAttachment'
        reused:
          type: boolean
          description: >-
            True when a folder with the same roster (member paths and declared
            sizes; contents are not compared) was already open and ready and is
            handed back; `members` is empty and nothing needs uploading.
        members:
          type: array
          items:
            $ref: '#/components/schemas/ConversationAttachmentReservation'
        skipped:
          type: array
          description: >-
            Files the instance will not read. Always present; empty when nothing
            was skipped.
          items:
            $ref: '#/components/schemas/ConversationAttachmentFolderSkip'
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: >
            Stable snake_case error identifier. Safe to match programmatically.

            New codes are added over time; treat an unknown code by its HTTP
            status.


            Known codes include:

            `invalid_request`, `invalid_input`, `unauthorized`,

            `auth_unavailable`, `rate_limited`, `missing_run_id`,

            `invalid_run_id`, `run_not_found`, `invalid_offerings`,

            `missing_input`, `queue_unreachable`, `engine_not_found`,

            `engine_slug_conflict`, `engine_archived`, `instance_not_found`,

            `instance_slug_conflict`, `instance_archived`,

            `instance_follows_canonical`, `instance_not_published`,

            `invalid_engine_type`, `validation_error`, `webhook_not_found`,

            `webhook_limit_exceeded`, `invalid_url`, `invalid_events`,

            `invalid_description`, `invalid_auth_token`,

            `missing_endpoint_id`, `invalid_endpoint_id`,

            `missing_delivery_id`, `invalid_delivery_id`,

            `delivery_not_found`, `delivery_not_resendable`,

            `insufficient_capability`, `engine_version_required`,

            `engine_version_exact_required`, `engine_version_not_found`,

            `engine_version_invalid_format`,

            `engine_version_draft_requires_sandbox_key`,

            `engine_version_none_released`, `test_scenario_sandbox_only`,

            `test_scenario_forbidden`, `test_scenario_exact_version_required`,

            `test_scenario_version_not_supported`, `invalid_test_scenario`,

            `request_bound_exceeded`, `run_cap_exceeded`,

            `scoped_key_required`, `engine_binding_forbidden`,

            `request_timeout`, `internal_error`, `idempotency_key_reused`,

            `invalid_idempotency_key`, `acting_principal_mismatch`,

            `run_requires_acting_principal`, `environment_slug_invalid`,

            `environment_slug_reserved`, `environment_slug_taken`,

            `environment_name_invalid`, `environment_limit_reached`,

            `environment_live_immutable`, `environment_not_found`,

            `environment_in_use`, `environment_operation_failed`,

            `list_environments_failed`, `engine_release_unservable`,

            `engine_config_hash_inconsistent`,

            `engine_config_hash_unavailable`,

            `engine_config_version_unavailable`,

            `engine_version_resolve_failed`,

            `engine_config_version_load_failed`,

            `engine_version_publish_mismatch`,

            `engine_version_schema_change_requires_major`,

            `engine_config_hash_missing`, `engine_config_changed`,

            `invalid_engine_config`, `cold_start_gate_not_met`,

            `cold_start_gate_failed`, `cold_start_gate_regressed`,

            `cancel_run_failed`, `event_id_reused`, `invalid_event_id`,

            `missing_event_id`, `invalid_event_type`, `invalid_payload`,

            `reason_text_too_long`, `reason_taxonomy_version_unknown`,

            `invalid_rating`, `missing_comment`, `invalid_target`,

            `invalid_time_on_task`, `scoped_annotation_required`,

            `missing_instance_slug`, `invalid_instance_config`,

            `invalid_eval_waiver`, `instance_config_hash_missing`,

            `config_changed_during_publish`,

            `scenario_set_changed_during_publish`,

            `conversation_eval_regressed`,

            `conversation_eval_execution_failed`,

            `conversation_eval_gate_unavailable`, `publish_failed`,

            `conversation_not_found`, `conversation_archived`,

            `invalid_cursor`, `message_not_found`, `invalid_turn_request`,

            `message_too_long`, `invalid_tool_result`, `invalid_confirmation`,

            `invalid_edit_target`, `turn_in_progress`, `pending_turn`,

            `turn_state_conflict`, `confirmation_environment_unpinned`,

            `instance_config_missing`, `instance_config_invalid`,

            `instance_model_unavailable`, `guardrail_evaluation_failed`,

            `guardrail_config_invalid`, `invalid_conversation_history`,

            `streaming_unsupported`, `converse_unavailable`, `provider_error`,

            `provider_unavailable`, `unknown_model`, `too_many_messages`,

            `too_many_tools`, `invalid_max_tokens`, `invalid_message_role`,

            `invalid_message`, `invalid_content_block`, `invalid_tool`,

            `attachments_not_enabled`, `attachments_unavailable`,

            `attachment_rejected`, `attachment_too_large`,

            `attachments_too_large`, `too_many_attachments`,

            `attachment_not_accepted`, `attachment_not_found`,

            `attachment_changed`, `attachment_read_failed`,

            `attachment_member_delete`, `attachment_not_reservable`,

            `invalid_offset`, `invalid_turn_id`, `invalid_comment`,

            `invalid_reason`, `invalid_feedback_key`, `turn_not_found`,

            `annotation_not_found`, `annotation_not_promotable`,

            `invalid_eval_scenario`, `conversation_eval_scenario_not_found`,

            `conversation_eval_scenario_cap`,

            `conversation_eval_run_not_found`,

            `conversation_eval_baseline_not_found`,

            `conversation_eval_unavailable`,

            `instance_version_invalid_format`, `instance_version_not_found`,

            `document_class_not_open`, `presign_failed`,

            `markets_directory_unavailable`, `book_unavailable`,

            `book_connection_ambiguous`, `identity_unmapped`,

            `identity_needs_review`, `identity_suspended`, `identity_stale`,

            `scope_unavailable`, `assertion_invalid`, `assertion_stale`,

            `cursor_filter_mismatch`, `cursor_expired`,

            `action_schema_unknown`, `action_payload_invalid`,

            `action_out_of_scope`, `action_list_too_large`,

            `overlay_read_only`, `unsupported_node_type`,

            `acting_principal_required`, `action_denied`,

            `appetite_read_denied`, `approval_required`,

            `catalog_access_denied`, `catalog_connection_ambiguous`,

            `create_webhook_failed`, `dataset_denied`, `delivery_id_reused`,

            `document_type_not_servable`, `document_unclassified`,

            `egress_manifest_version_mismatch`,

            `egress_manifest_version_required`, `event_type_not_allowed`,

            `event_type_reserved`, `execution_confirm_required`,

            `execution_prohibited`, `ingest_source_disabled`,

            `ingest_source_misconfigured`, `ingest_source_not_found`,

            `invalid_acting_assertion`, `invalid_active`,

            `invalid_credentials`, `invalid_payload_mode`,

            `invalid_signature`, `load_run_failed`, `load_solutions_failed`,

            `load_work_items_failed`, `missing_engine_slug`, `not_found`,

            `object_store_unconfigured`, `provider_not_approved`,

            `resolve_retry_exhausted`, `run_lookup_failed`,

            `service_identity_unknown`, `stale_timestamp`, `surface_denied`,

            `unresolved_market_question`, `unsupported_market_filter`.


            The full list with causes and fixes is at

            https://docs.usenexio.com/reference/errors.
        message:
          type: string
          description: Human-readable error message. May change between versions.
        details:
          description: |
            Optional request-specific details. Request-bound failures use the
            `RequestBoundDetails` object. Validation failures may use an array
            of field issues or another documented object.

            `409 environment_in_use` is the one exception to this envelope: it
            carries a top-level `blockers` object (see `EnvironmentInUseError`)
            instead of `details`.
    ConversationAttachment:
      type: object
      description: |
        One file attached to a conversation. There is no text field: the
        platform does not parse the document, it hands the bytes to the model.
      required:
        - id
        - filename
        - media_type
        - size_bytes
        - status
        - delivery
        - created_at
      properties:
        id:
          type: string
          format: uuid
        filename:
          type: string
        media_type:
          type: string
        size_bytes:
          type: integer
          format: int64
        status:
          type: string
          enum:
            - storing
            - unwrapping
            - ready
            - rejected
            - failed
          description: |
            `ready` can ride a turn. A multipart upload answers `ready`. A
            reserved upload is `storing` until finalize; a folder container
            is `unwrapping` until every member is finalized. A finalize that
            finds no bytes leaves the record `storing`. A finalize that refuses
            the file for any other reason marks it `rejected` and removes it at
            once, so it no longer lists or reads. `failed` is reserved and not
            currently set. Only `ready` records can ride a turn or be served.
        delivery:
          type: string
          enum:
            - file
            - image
            - unwrap
          description: |
            How the file reaches the model. `file` and `image` are provider
            inputs. `unwrap` is a container, which never reaches the model
            itself; a turn naming it carries the files inside it.

            A folder container also has delivery `unwrap`.
        notice:
          type: string
          description: >
            How this format is read, when there is a limit worth knowing. On a

            zip or email container it says how many files were read and what

            was left out; on a folder container it says how many files the

            folder holds. A spreadsheet is read to the first 1,000 rows per
            sheet; text is

            read out of Office files while images and charts inside them are

            not. Absent when the file is read whole. Show it: an answer drawn

            from part of a schedule is not an answer about the schedule.
        error:
          type: string
          description: Why the record was rejected or failed. Absent otherwise.
        container_kind:
          type: string
          enum:
            - zip
            - eml
            - folder
          description: |
            What a container is. `zip` and `eml` are opened by the platform;
            `folder` is opened by the client and its files uploaded one by
            one. Set on a folder, and on a zip or email stored through
            finalize. Absent on a zip or email stored by a multipart upload,
            on a plain file, and on a file inside a container.
        parent_attachment_id:
          type: string
          format: uuid
          description: The container this file was found inside, when it was.
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: |
            When access to the attachment ends. From this moment it is left
            out of lists, answers `404` when read or downloaded, and cannot be
            named on a turn. A later retention sweep deletes the record and
            removes the bytes. Absent when the attachment follows its
            conversation's own retention.
    ConversationAttachmentReservation:
      type: object
      description: One reserved attachment record plus the write grant for its bytes.
      required:
        - attachment
        - upload_url
        - upload_url_expires_at
      properties:
        attachment:
          $ref: '#/components/schemas/ConversationAttachment'
        upload_url:
          type: string
          format: uri
          description: |
            Presigned PUT URL for this one file. Send the raw bytes with no
            `Authorization` header. It cannot read and cannot reach any other
            file.
        upload_url_expires_at:
          type: string
          format: date-time
          description: When `upload_url` stops working, 15 minutes after it was issued.
        source_path:
          type: string
          description: >-
            For a folder member, the `path` your request sent for it, echoed
            verbatim. Absent on a standalone reservation.
    ConversationAttachmentFolderSkip:
      type: object
      description: One file in a folder that the instance will not read.
      required:
        - path
        - reason
      properties:
        path:
          type: string
          description: The path as your request sent it.
        reason:
          type: string
          description: Why it was skipped, in words a person can act on.
  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: unauthorized
            message: Missing or invalid API key
    RateLimited:
      description: Rate limit exceeded. Retry after the window resets.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: rate_limited
            message: Rate limit exceeded
    AttachmentsUnavailable:
      description: >
        `attachments_unavailable`: file attachments are not available in this

        environment because the platform has no attachment storage wired. That

        cause is not retryable by the caller; the instance's operator has to

        enable attachment storage.


        Also `auth_unavailable`: API key authentication was briefly unavailable
        before the route ran. That cause is transient; retry it with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: attachments_unavailable
            message: File attachments are not available in this environment.
    RequestTimeout:
      description: >-
        `request_timeout`: the 30 second route timeout expired before the
        handler answered.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: request_timeout
            message: Request timed out
  headers:
    RetryAfter:
      description: Whole seconds to wait before retrying, at least 1.
      schema:
        type: integer
        minimum: 1
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        Send the key as `Authorization: Bearer <key>`. Two kinds of key exist.


        Organization keys are issued in the portal (Settings, then API keys),

        each bound to one environment, shaped `nx_<environment slug>_<64 hex>`.
        They carry

        no capabilities and pass every capability check, with one exception:

        routes under /api/v1/records, /api/v1/engines/{id}/opportunities,

        /api/v1/graph and /api/v1/catalog/documents accept an

        organization key only when its environment is `live`, and refuse any

        other with 403 `scoped_key_required`. Revocation takes effect within 60

        seconds.


        Scoped keys are issued by Nexio on request, shaped

        `nxsk_v1_<24 hex key id>_<43 character secret>`. Each is bound to one

        org, one environment, a set of engines and a set of capabilities. A

        malformed, unknown or revoked `nxsk_` key fails with 401 and is never

        retried as an organization key. Revocation takes effect on the next

        request. A scoped key without a route's capability gets 403

        `insufficient_capability`; a scoped key not bound to the engine gets 403

        `engine_binding_forbidden`.


        Key-grantable capabilities: `engines:read`, `runs:write`, `runs:read`,

        `runs:defensibility:read`, `runs:test`, `catalog:read`,

        `catalog:documents:read`, `webhooks:manage`, `conversations:use`,

        `conversations:export`, `records:read`, `records:opportunities:run`,

        `actions:write`, `actions:read`, `graph:read`, `records:analyze`.


        Routes that accept organization keys only (every scoped key gets 403

        `insufficient_capability`): environment management, engine create,

        update, configuration and publish, and conversation instance

        authoring. Each operation description names the capability a scoped

        key needs.

````