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

# Upload a file

> Stores one file for the account and returns its public address. This is how a user's own images,
video and PDFs reach a widget's content.

Send a `multipart/form-data` request with the file in the field named `file`. The file counts
against the storage of the workspace it is uploaded in, and a full workspace is refused with 413 and
`QUOTA_STORAGE_EXCEEDED`. For a person signed in on widgetic.com that is the workspace they are currently
working in: inside a team, the team's shared storage; inside their own workspace, their own allowance. An
organization API key, and a Shopify store's session, always work in that team's shared storage. A personal
API key or an SDK token always uses the account's own allowance. The two never add up and one never lends
room to the other. A file over the size limit is refused with 413 and `UPLOAD_TOO_LARGE`, and a
type that is not accepted with 415 and `UPLOAD_UNSUPPORTED_TYPE`; `getUploadLimits` lists the size
limit and the accepted types.

A file is judged by what is in it, not by the type it is sent as. A file that is not the type it claims (a
web page sent as `image/png`) is refused with 415 and `UPLOAD_UNSUPPORTED_TYPE`, and a picture sent under
the wrong picture type is stored under the type it really is, which `mime_type` in the answer says. An SVG
is rewritten as it is stored: script, `foreignObject`, animation and references to other addresses are
taken out, it must be UTF-8 and at most 2 MB, and the stored file can be larger or smaller than the one
sent. Anything that is not an image, audio or video (a PDF, for example) is served as a download rather
than shown in a page.

When the full storage is a Shopify store's team's, extra storage cannot be added through Shopify yet, so the
message ends by saying so and what to do instead: delete files, or contact support. On a Solo plan it names
the Teams plan (which holds more) and `error.details.planPageUrl` is the address of the plan page on Shopify.
`error.suggestedAction` says the same in a few words, instead of the usual "upgrade your storage plan". Any
other team, and a person's own workspace, get the shorter message.

To upload the file for one of your managed users, send `managedUserId` (Widgetic's id for the user) or
`managedUserExternalId` (your own identifier for the user), not both. The file stays in your account and
counts against its allowance; the tag lets you list one user's files with `listUploads`. The managed user
must be yours and active: one that is not yours, or does not exist, is refused with 404 and
`MANAGED_USER_NOT_FOUND`, and one that is switched off with 409 and `MANAGED_USER_INACTIVE`, before the
file is stored. The tag is set here and does not change afterwards.

Called with an editor session (`createManagedUserEditorSession`), the file is always filed for the
session's own managed user and is always media for widget content: naming another managed user is
refused with 403 and `MANAGED_USER_SESSION_MISMATCH`, and `contextType`, `source`, `widgetId` and
`conversationId` are ignored. A full storage allowance is refused with the same 413 and
`QUOTA_STORAGE_EXCEEDED`, but the message only says that it is not available right now (not how much is
stored: that is the partner's business) and `error.details.limit` is `storage`.

An account that administers a partner with a storage destination (`setPartnerStorageDestination`) has
its files stored in the partner's own bucket instead. Such a file takes none of the allowance, its `url`
is served from the partner's public address, and its `storage_key` is the key in the partner's bucket.
The file is never stored with Widgetic instead: when the destination cannot be used the upload is refused
with 503 and `STORAGE_DESTINATION_UNAVAILABLE`, and when the partner's storage rejects the file with
502 and `STORAGE_DESTINATION_WRITE_FAILED`.

Partner storage is not optional, and it does not depend on how the file arrives. A partner's own uploads (with
its API key, an SDK token, or from the Widgetic website, the resumable route and the media library alike)
and the uploads of its customers' editor sessions are kept in the partner's own storage or not at all, so a
partner with no destination (or one that removed it after the session was made) has them refused with 409
and `PARTNER_STORAGE_REQUIRED`; they are never stored with Widgetic. An editor session's browser is only
told that the feature is not available right now.




## OpenAPI

````yaml /openapi/widgetic-api-public.json post /uploads
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:
  /uploads:
    post:
      tags:
        - Uploads
      summary: Upload a file
      description: >
        Stores one file for the account and returns its public address. This is
        how a user's own images,

        video and PDFs reach a widget's content.


        Send a `multipart/form-data` request with the file in the field named
        `file`. The file counts

        against the storage of the workspace it is uploaded in, and a full
        workspace is refused with 413 and

        `QUOTA_STORAGE_EXCEEDED`. For a person signed in on widgetic.com that is
        the workspace they are currently

        working in: inside a team, the team's shared storage; inside their own
        workspace, their own allowance. An

        organization API key, and a Shopify store's session, always work in that
        team's shared storage. A personal

        API key or an SDK token always uses the account's own allowance. The two
        never add up and one never lends

        room to the other. A file over the size limit is refused with 413 and
        `UPLOAD_TOO_LARGE`, and a

        type that is not accepted with 415 and `UPLOAD_UNSUPPORTED_TYPE`;
        `getUploadLimits` lists the size

        limit and the accepted types.


        A file is judged by what is in it, not by the type it is sent as. A file
        that is not the type it claims (a

        web page sent as `image/png`) is refused with 415 and
        `UPLOAD_UNSUPPORTED_TYPE`, and a picture sent under

        the wrong picture type is stored under the type it really is, which
        `mime_type` in the answer says. An SVG

        is rewritten as it is stored: script, `foreignObject`, animation and
        references to other addresses are

        taken out, it must be UTF-8 and at most 2 MB, and the stored file can be
        larger or smaller than the one

        sent. Anything that is not an image, audio or video (a PDF, for example)
        is served as a download rather

        than shown in a page.


        When the full storage is a Shopify store's team's, extra storage cannot
        be added through Shopify yet, so the

        message ends by saying so and what to do instead: delete files, or
        contact support. On a Solo plan it names

        the Teams plan (which holds more) and `error.details.planPageUrl` is the
        address of the plan page on Shopify.

        `error.suggestedAction` says the same in a few words, instead of the
        usual "upgrade your storage plan". Any

        other team, and a person's own workspace, get the shorter message.


        To upload the file for one of your managed users, send `managedUserId`
        (Widgetic's id for the user) or

        `managedUserExternalId` (your own identifier for the user), not both.
        The file stays in your account and

        counts against its allowance; the tag lets you list one user's files
        with `listUploads`. The managed user

        must be yours and active: one that is not yours, or does not exist, is
        refused with 404 and

        `MANAGED_USER_NOT_FOUND`, and one that is switched off with 409 and
        `MANAGED_USER_INACTIVE`, before the

        file is stored. The tag is set here and does not change afterwards.


        Called with an editor session (`createManagedUserEditorSession`), the
        file is always filed for the

        session's own managed user and is always media for widget content:
        naming another managed user is

        refused with 403 and `MANAGED_USER_SESSION_MISMATCH`, and `contextType`,
        `source`, `widgetId` and

        `conversationId` are ignored. A full storage allowance is refused with
        the same 413 and

        `QUOTA_STORAGE_EXCEEDED`, but the message only says that it is not
        available right now (not how much is

        stored: that is the partner's business) and `error.details.limit` is
        `storage`.


        An account that administers a partner with a storage destination
        (`setPartnerStorageDestination`) has

        its files stored in the partner's own bucket instead. Such a file takes
        none of the allowance, its `url`

        is served from the partner's public address, and its `storage_key` is
        the key in the partner's bucket.

        The file is never stored with Widgetic instead: when the destination
        cannot be used the upload is refused

        with 503 and `STORAGE_DESTINATION_UNAVAILABLE`, and when the partner's
        storage rejects the file with

        502 and `STORAGE_DESTINATION_WRITE_FAILED`.


        Partner storage is not optional, and it does not depend on how the file
        arrives. A partner's own uploads (with

        its API key, an SDK token, or from the Widgetic website, the resumable
        route and the media library alike)

        and the uploads of its customers' editor sessions are kept in the
        partner's own storage or not at all, so a

        partner with no destination (or one that removed it after the session
        was made) has them refused with 409

        and `PARTNER_STORAGE_REQUIRED`; they are never stored with Widgetic. An
        editor session's browser is only

        told that the feature is not available right now.
      operationId: uploadFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to store
                contextType:
                  type: string
                  enum:
                    - widget-codegen
                    - browser-ic
                    - content-property
                  default: content-property
                  description: >
                    What the file is for. Send `content-property` for media that
                    becomes widget content (images, video and PDF); the other
                    values are used by Widgetic's own editor.
                source:
                  type: string
                  enum:
                    - chat-upload
                    - chat-screenshot
                    - browser-ic-screenshot
                    - preview-screenshot
                    - file-browser-upload
                  default: file-browser-upload
                  description: >-
                    Which part of the product, or which client, the file comes
                    from
                managedUserId:
                  type: string
                  format: uuid
                  description: >-
                    Upload the file for one of your managed users, by Widgetic's
                    id for the user. Send this or `managedUserExternalId`, not
                    both.
                managedUserExternalId:
                  type: string
                  maxLength: 255
                  description: >-
                    The same, by your own identifier for the user, the
                    `externalId` you gave when you created the managed user.
                    Send this or `managedUserId`, not both.
                purpose:
                  type: string
                  enum:
                    - canvas
                    - chat
                    - field
                  description: >-
                    Where in your product you use the file. It is kept with the
                    file, so that a customer's storage can be counted by purpose
                    (`getManagedUserStorageUsage`). Optional; any other value is
                    refused with 400 and `UPLOAD_INVALID_PURPOSE`.
      responses:
        '200':
          description: The stored file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
        '400':
          description: >-
            No file was sent, more than one was, or it was not in the field
            named `file`; or a managed user field is not valid
            (`MANAGED_USER_REFERENCE_INVALID`, or
            `MANAGED_USER_REFERENCE_AMBIGUOUS` when both are sent); or `purpose`
            is not one of the three (`UPLOAD_INVALID_PURPOSE`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: The API key is missing or invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            An editor session named a managed user other than its own
            (`MANAGED_USER_SESSION_MISMATCH`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: The managed user is not one of yours (`MANAGED_USER_NOT_FOUND`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            The managed user is switched off, so nothing new can be uploaded for
            it (`MANAGED_USER_INACTIVE`); or an editor session uploaded and the
            partner has no storage destination (`PARTNER_STORAGE_REQUIRED`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: >-
            The file is over the size limit (`UPLOAD_TOO_LARGE`), or the storage
            of the workspace the file is for is full (`QUOTA_STORAGE_EXCEEDED`;
            for a Shopify store's team the message also says what to do, and on
            a Solo plan `error.details.planPageUrl` is the plan page)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '415':
          description: >-
            The file type is not accepted, or the contents are not the type the
            file was sent as (`UPLOAD_UNSUPPORTED_TYPE`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: >-
            The partner's own storage did not accept the file
            (`STORAGE_DESTINATION_WRITE_FAILED`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: >-
            The account's storage destination cannot be used right now
            (`STORAGE_DESTINATION_UNAVAILABLE`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - apiKeyAuth: []
components:
  schemas:
    UploadResponse:
      type: object
      required:
        - success
        - data
      properties:
        success:
          type: boolean
          description: Always `true` on a success.
          example: true
        data:
          $ref: '#/components/schemas/Upload'
        meta:
          $ref: '#/components/schemas/ResponseMeta'
    ErrorResponse:
      type: object
      required:
        - success
        - message
        - error
      additionalProperties: true
      properties:
        success:
          type: boolean
          description: Always `false` on a failure.
          example: false
        message:
          type: string
          description: The same text as `error.message`, for a caller that reads one line.
        error:
          $ref: '#/components/schemas/ErrorDetail'
    Upload:
      type: object
      description: A file stored for the account that uploaded it.
      required:
        - id
        - url
        - mime_type
        - file_name
        - file_size
        - storage_key
        - context_type
        - source
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          description: The public address of the stored file
        mime_type:
          type: string
          description: >-
            The type of the stored file. A HEIC or HEIF photo is stored as
            `image/jpeg`.
          example: image/png
        file_name:
          type: string
        file_size:
          type: integer
          description: Size of the stored file, in bytes
        width:
          type: integer
          nullable: true
          description: Width in pixels, for images
        height:
          type: integer
          nullable: true
          description: Height in pixels, for images
        storage_key:
          type: string
          description: Where the file sits in its storage bucket
        context_type:
          type: string
          enum:
            - widget-codegen
            - browser-ic
            - content-property
          description: >-
            What the file was uploaded for. Media for widget content is
            `content-property`.
        source:
          type: string
          enum:
            - chat-upload
            - chat-screenshot
            - browser-ic-screenshot
            - preview-screenshot
            - file-browser-upload
            - widget-visitor-upload
          description: >-
            Which part of the product, or which client, uploaded the file.
            `widget-visitor-upload` is a file a visitor sent through a widget's
            upload adapter; it cannot be asked for when uploading.
        widget_id:
          type: string
          format: uuid
          nullable: true
        conversation_id:
          type: string
          format: uuid
          nullable: true
        managed_user_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            The managed user the file was uploaded for, or null when it is the
            account's own. List one managed user's files with the
            `managed_user_id` or `managed_user_external_id` query parameter of
            `listUploads`.
        purpose:
          type: string
          nullable: true
          enum:
            - canvas
            - chat
            - field
          description: Where in the product the partner uses the file, or null when unknown
      example:
        id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        url: Example url
        mime_type: image/png
        file_name: Example file_name
        file_size: 42
        width: 42
        height: 42
        storage_key: Example storage_key
        context_type: Example context_type
        source: Example source
        widget_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        conversation_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        managed_user_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        purpose: Example purpose
    ResponseMeta:
      type: object
      description: >-
        Extra information about the answer: where a page sits in a list, the
        cursor of the next page, or warnings about a call that succeeded with a
        caveat.
      additionalProperties: true
      properties:
        page:
          type: integer
          description: The page that was read, counting from 1.
          example: 1
        size:
          type: integer
          description: How many items one page holds.
          example: 20
        total:
          type: integer
          description: How many items the whole list has.
          example: 135
        limit:
          type: integer
          description: The same as `size`.
          example: 20
        offset:
          type: integer
          description: How many items come before this page.
          example: 0
        hasNextPage:
          type: boolean
          example: true
        hasPreviousPage:
          type: boolean
          example: false
        nextCursor:
          type: string
          nullable: true
          description: >-
            Pass this as `cursor` to read the next page of a list that is read
            by cursor. `null` on the last page.
        warnings:
          type: array
          items:
            type: string
          description: >-
            Things the call did not do, though it succeeded (for example a table
            that could not be created just now).
    ErrorDetail:
      type: object
      required:
        - code
        - message
        - timestamp
      properties:
        code:
          type: string
          description: Machine-readable error code.
          example: RESOURCE_NOT_FOUND
        message:
          type: string
          description: Human-readable explanation.
          example: Composition not found
        param:
          type: string
          description: The field that caused the error, when one did.
          example: name
        docUrl:
          type: string
          description: Where this error is explained.
        suggestedAction:
          type: string
          description: What to do about it.
        retryAfter:
          type: integer
          description: >-
            Seconds to wait before trying again (rate limits). The same number
            is in the `Retry-After` header.
          example: 60
        details:
          type: object
          additionalProperties: true
          description: >-
            More about the error: the fields that failed, a limit that was
            reached.
        correlationId:
          type: string
          description: Quote this when you ask support about the call.
          example: req_123e4567-e89b-12d3-a456-426614174000
        timestamp:
          type: string
          format: date-time
          example: '2026-10-15T10:00:00.000Z'
  securitySchemes:
    apiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````

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