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

# Send the file a visitor chose through the upload adapter of a published widget

> The second of the two calls the `upload` adapter's module makes from the visitor's browser (the first, the `begin-upload` operation of `callPublicCompositionAdapter`, answers with the ticket this route takes). The module sends the file itself, as the body, with its media type as the `Content-Type`. It is not meant to be called by hand, and it takes no API key.
**Address.** `https://api.widgetic.com/embed/compositions/{compositionId}/adapters/upload/files/{ticket}`: the root of the API host, NOT under `/v1`. A client generated from this description for `https://api.widgetic.com/v1` must be given the base address `https://api.widgetic.com` for the calls of the `Public Embed` tag.
**The ticket.** Single-use and valid for ten minutes, given for one file: its name, its media type and its exact size are what the visitor said when the ticket was asked for, after the widget's owner's settings (the kinds of file they take, the largest size) were applied. The `Content-Type` and the `Content-Length` of this request must be those, and the body must be exactly that many bytes (400 `UPLOAD_FILE_CHANGED` otherwise). The gateway never believes the type it was told: the first bytes of the file must be a file of that type and one the owner accepts (422 otherwise). A ticket whose file was stored and is sent again is answered with the same file, so a visitor whose connection dropped after the file landed does not store it twice; a ticket whose attempt failed is spent, and the module asks for a new one.
**The virus check.** Before anything is stored, the file is checked for viruses, wherever it would be kept (Widgetic's storage or a partner's own bucket). A file that fails the check is refused with 422 `UPLOAD_FILE_UNSAFE` and nothing is stored. When the check cannot be done, nothing is stored either, and the answer is the same neutral 503 `UPLOAD_UNAVAILABLE` as for any other cause the visitor is not told. The ticket of a refused file is spent.
**What it stores.** The file goes to the widget owner's storage: Widgetic's, or the partner's own bucket for a widget of a customer a partner manages. It counts against the owner's storage allowance and is filed in the folder the owner chose, as a file sent by a visitor, which the owner's own file list leaves out. A full allowance, a partner's storage that cannot be used and a stopped feature are all the same 503 `UPLOAD_UNAVAILABLE` to a visitor, and the owner is told through a notice.
**Who may call.** The same rule as the entry route: a permitted `Origin` header, which is the artifact origin the widget runs from or any website when the owner has not restricted the allowed websites (when the owner has, one of the websites the owner listed, Widgetic's own sites, the visitor's own machine or the owner's own platform). A request without a permitted `Origin` is 403 `ORIGIN_NOT_ALLOWED`. A composition that is not served (unpublished, or its owner's plan does not serve it) is 404, with the codes of the other public embed routes.
**Limits.** 30 requests a minute for each visitor address and composition and 240 a minute for a composition (429 `RATE_LIMITED`, with a `Retry-After` header); at most 10 MB for a file, and only so many files held in memory at once on each gateway process (503 `UPLOAD_BUSY`, with a `Retry-After`). A file that does not arrive within two minutes is 408 `UPLOAD_TIMEOUT`. Every answer but a success closes the connection, so a client that is refused before it has sent the file reads the answer and stops.



## OpenAPI

````yaml /openapi/widgetic-api-public.json put /embed/compositions/{compositionId}/adapters/upload/files/{ticket}
openapi: 3.0.0
info:
  title: Widgetic API Gateway
  version: 1.0.0
  description: API documentation for the Widgetic API Gateway
  license:
    name: Widgetic Integration License
    url: https://docs.widgetic.com/resources/license
servers:
  - url: https://api.widgetic.com/v1
    description: Production server
    variables:
      env:
        default: production
        description: Environment
      apiUrl:
        default: https://api.widgetic.com
        description: API URL
      apiVersion:
        default: /v1
        description: API Version path prefix
security: []
tags: []
paths:
  /embed/compositions/{compositionId}/adapters/upload/files/{ticket}:
    servers:
      - url: https://api.widgetic.com
        description: >-
          The public embed routes are served at the root of the API host, not
          under /v1.
    put:
      tags:
        - Public Embed
      summary: >-
        Send the file a visitor chose through the upload adapter of a published
        widget
      description: >-
        The second of the two calls the `upload` adapter's module makes from the
        visitor's browser (the first, the `begin-upload` operation of
        `callPublicCompositionAdapter`, answers with the ticket this route
        takes). The module sends the file itself, as the body, with its media
        type as the `Content-Type`. It is not meant to be called by hand, and it
        takes no API key.

        **Address.**
        `https://api.widgetic.com/embed/compositions/{compositionId}/adapters/upload/files/{ticket}`:
        the root of the API host, NOT under `/v1`. A client generated from this
        description for `https://api.widgetic.com/v1` must be given the base
        address `https://api.widgetic.com` for the calls of the `Public Embed`
        tag.

        **The ticket.** Single-use and valid for ten minutes, given for one
        file: its name, its media type and its exact size are what the visitor
        said when the ticket was asked for, after the widget's owner's settings
        (the kinds of file they take, the largest size) were applied. The
        `Content-Type` and the `Content-Length` of this request must be those,
        and the body must be exactly that many bytes (400 `UPLOAD_FILE_CHANGED`
        otherwise). The gateway never believes the type it was told: the first
        bytes of the file must be a file of that type and one the owner accepts
        (422 otherwise). A ticket whose file was stored and is sent again is
        answered with the same file, so a visitor whose connection dropped after
        the file landed does not store it twice; a ticket whose attempt failed
        is spent, and the module asks for a new one.

        **The virus check.** Before anything is stored, the file is checked for
        viruses, wherever it would be kept (Widgetic's storage or a partner's
        own bucket). A file that fails the check is refused with 422
        `UPLOAD_FILE_UNSAFE` and nothing is stored. When the check cannot be
        done, nothing is stored either, and the answer is the same neutral 503
        `UPLOAD_UNAVAILABLE` as for any other cause the visitor is not told. The
        ticket of a refused file is spent.

        **What it stores.** The file goes to the widget owner's storage:
        Widgetic's, or the partner's own bucket for a widget of a customer a
        partner manages. It counts against the owner's storage allowance and is
        filed in the folder the owner chose, as a file sent by a visitor, which
        the owner's own file list leaves out. A full allowance, a partner's
        storage that cannot be used and a stopped feature are all the same 503
        `UPLOAD_UNAVAILABLE` to a visitor, and the owner is told through a
        notice.

        **Who may call.** The same rule as the entry route: a permitted `Origin`
        header, which is the artifact origin the widget runs from or any website
        when the owner has not restricted the allowed websites (when the owner
        has, one of the websites the owner listed, Widgetic's own sites, the
        visitor's own machine or the owner's own platform). A request without a
        permitted `Origin` is 403 `ORIGIN_NOT_ALLOWED`. A composition that is
        not served (unpublished, or its owner's plan does not serve it) is 404,
        with the codes of the other public embed routes.

        **Limits.** 30 requests a minute for each visitor address and
        composition and 240 a minute for a composition (429 `RATE_LIMITED`, with
        a `Retry-After` header); at most 10 MB for a file, and only so many
        files held in memory at once on each gateway process (503 `UPLOAD_BUSY`,
        with a `Retry-After`). A file that does not arrive within two minutes is
        408 `UPLOAD_TIMEOUT`. Every answer but a success closes the connection,
        so a client that is refused before it has sent the file reads the answer
        and stops.
      operationId: uploadPublicCompositionAdapterFile
      parameters:
        - in: path
          name: compositionId
          required: true
          schema:
            type: string
            format: uuid
        - in: path
          name: ticket
          required: true
          description: The ticket the `begin-upload` operation answered with.
          schema:
            type: string
            minLength: 20
            maxLength: 128
            pattern: ^[A-Za-z0-9_-]{20,128}$
      requestBody:
        required: true
        content:
          application/pdf:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/aac:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/flac:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/mp4:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/mpeg:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/ogg:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/wav:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          audio/webm:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          image/gif:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          image/heic:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          image/heif:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          image/jpeg:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          image/png:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          image/webp:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          video/mp4:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          video/quicktime:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
          video/webm:
            schema:
              $ref: '#/components/schemas/PublicAdapterFileBytes'
      responses:
        '200':
          description: The file is stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterFileResult'
        '400':
          description: >-
            What arrived is not the file the ticket was given for
            (`UPLOAD_FILE_CHANGED`: another type, another size, or a body that
            ended early or ran long).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '403':
          description: Origin not allowed (`ORIGIN_NOT_ALLOWED`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '404':
          description: >-
            The ticket is not valid, has expired or belongs to another widget
            (`UPLOAD_TICKET_INVALID`), the widget no longer uses the adapter or
            its owner switched it off (`ADAPTER_NOT_AVAILABLE`), or the
            composition is not there or not served (`NOT_FOUND`, `UNPUBLISHED`,
            `SUSPENDED`; an id that is not a UUID is `NOT_FOUND` too).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '408':
          description: The file did not arrive in time (`UPLOAD_TIMEOUT`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '409':
          description: >-
            The ticket was already used and its file was not stored
            (`UPLOAD_TICKET_USED`). Ask for a new ticket.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '411':
          description: >-
            The request did not say how long the file is
            (`UPLOAD_LENGTH_REQUIRED`): send `Content-Length`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '413':
          description: >-
            The file is larger than the owner allows or than the platform takes
            (`UPLOAD_TOO_LARGE`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '415':
          description: >-
            Not a media type the upload adapter takes
            (`UNSUPPORTED_MEDIA_TYPE`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '422':
          description: >-
            The bytes are not a file of the kind they say
            (`UPLOAD_FILE_UNRECOGNIZED`, `UPLOAD_FILE_MISMATCH`), not a kind
            this widget's owner accepts (`UPLOAD_TYPE_NOT_ACCEPTED`), or a file
            the virus check refused (`UPLOAD_FILE_UNSAFE`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '429':
          description: >-
            A limit was reached (`code` is `RATE_LIMITED`); the `Retry-After`
            header says when to ask again. The body is the standard rate-limit
            envelope with the flat `code` beside it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicEmbedFailure'
        '502':
          description: The file could not be saved (`UPLOAD_FAILED`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
        '503':
          description: >-
            Uploads cannot be used right now (`UPLOAD_UNAVAILABLE` or
            `ADAPTER_UNAVAILABLE`; the cause is never said), or every place in
            memory is taken (`UPLOAD_BUSY`). Try again later.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicAdapterCallError'
components:
  schemas:
    PublicAdapterFileBytes:
      type: string
      format: binary
      description: 'The bytes of the file as they are: no encoding and no form around them.'
    PublicAdapterFileResult:
      type: object
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          required:
            - file
          properties:
            file:
              type: object
              required:
                - url
                - name
                - size
                - type
                - width
                - height
              properties:
                url:
                  type: string
                  format: uri
                  maxLength: 2048
                  description: Where the stored file is. Always an https address.
                name:
                  type: string
                  description: >-
                    The name it is stored under. It is not the name the visitor
                    gave, which is only kept as far as it is plain text.
                size:
                  type: integer
                  description: Its size in bytes.
                type:
                  type: string
                  description: Its media type, as proved from its bytes.
                width:
                  type: integer
                  nullable: true
                  description: The width in pixels of a picture, otherwise null.
                height:
                  type: integer
                  nullable: true
                  description: The height in pixels of a picture, otherwise null.
      example:
        success: true
        data: {}
    PublicAdapterCallError:
      type: object
      required:
        - success
        - error
        - code
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: >-
            A sentence a visitor may read. It never says why a feature is
            unavailable: not that its owner is out of credits, not what a
            balance is, not which limit was reached beyond the word
            `RATE_LIMITED` or `WIDGET_LIMIT_REACHED`. (On a 429 the standard
            rate-limit envelope is sent instead: `error` is then an object, and
            `code` beside it is the flat code.)
        code:
          type: string
          description: >-
            What a module switches on. The codes of the gateway itself are
            `ADAPTER_BODY_INVALID`, `CAPTCHA_REQUIRED`, `ADAPTER_NOT_AVAILABLE`,
            `REQUEST_ALREADY_USED`, `RATE_LIMITED`, `WIDGET_LIMIT_REACHED`,
            `ADAPTER_FAILED`, `ADAPTER_UNAVAILABLE` and `ADAPTER_TIMEOUT`, with
            `ORIGIN_NOT_ALLOWED`, `PAYLOAD_TOO_LARGE`, `UNSUPPORTED_MEDIA_TYPE`,
            `CONTEXT_SIGNATURE_REQUIRED`, `CONTEXT_SIGNATURE_INVALID`,
            `CONTEXT_SIGNATURE_EXPIRED`, `CONTEXT_SIGNATURE_UNAVAILABLE` and the
            codes of the serving gate (`NOT_FOUND`, `UNPUBLISHED`, `SUSPENDED`)
            it shares with the other public embed routes. An operation may add
            codes of its own (capital letters and `_`) for an input it refused.
        correlationId:
          type: string
          description: Quote it when asking for help with a call.
      example:
        success: false
        error: Example error
        code: Example code
        correlationId: Example correlationId
    PublicEmbedFailure:
      type: object
      required:
        - success
        - message
        - error
        - code
      description: >-
        What a public embed route answers when it refuses or fails. It is the
        standard error answer of the API (`success`, `message`, `error`), and
        the short `code` (with `details` and `correlationId` when the route has
        them) is also at the top level, because a page that embeds a composition
        branches on it there. `error.code` is the same word in the codes of the
        rest of the API where those have it: `NOT_FOUND` is
        `RESOURCE_NOT_FOUND`, `RATE_LIMITED` is `RATE_LIMIT_EXCEEDED`, and the
        others (`DOMAIN_NOT_ALLOWED`, `UNPUBLISHED`, `SUSPENDED`, the codes of
        the entry route) are the same in both places.
      properties:
        success:
          type: boolean
          enum:
            - false
        message:
          type: string
          description: What happened, for a person.
        code:
          type: string
          description: >-
            The short code a page branches on: `DOMAIN_NOT_ALLOWED`,
            `UNPUBLISHED`, `SUSPENDED`, `NOT_FOUND`, `RATE_LIMITED`, and for the
            entry route the codes listed with its answers.
          example: NOT_FOUND
        details:
          type: object
          additionalProperties: true
          description: >-
            What tells the cases of one code apart, such as `reason` of
            `DOMAIN_NOT_ALLOWED`.
          properties:
            reason:
              type: string
              description: For `DOMAIN_NOT_ALLOWED`, why the website was refused.
              enum:
                - domain_not_allowed
                - opaque_origin
            hasDomainsConfigured:
              type: boolean
              description: >-
                For `DOMAIN_NOT_ALLOWED`, always true (an owner who listed
                nothing is never refused).
        correlationId:
          type: string
          description: The id of the request, for the routes that carry it.
        error:
          type: object
          required:
            - code
            - message
            - timestamp
          properties:
            code:
              type: string
              example: RESOURCE_NOT_FOUND
            message:
              type: string
            details:
              type: object
              additionalProperties: true
            correlationId:
              type: string
            retryAfter:
              type: integer
              description: Seconds to wait before asking again, for a rate-limit refusal.
            timestamp:
              type: string
              format: date-time
      example:
        success: true
        message: Example message
        code: NOT_FOUND
        details: {}
        correlationId: Example correlationId
        error: {}

````

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