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

# Create an upload

> The two-step path for a large payload: ask for a signed URL, PUT the bytes, then reference the upload by ID.

When a payload is too big to inline, you do not POST the bytes to Cadenya. You ask for a place to put them, upload them directly to storage, and hand the resulting `upload_id` to whatever needs the data.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const upload = await client.uploads.create({
    workspaceId,
    metadata: { name: 'policy handbook' },
    spec: { filename: 'handbook.pdf', contentType: 'application/pdf', sizeBytes: '4194304' },
  });

  console.log(upload.info.uploadUrl);         // a signed storage URL
  console.log(upload.info.status);            // 'UPLOAD_STATUS_PENDING'
  ```

  ```go Go theme={null}
  upload, err := client.Uploads.New(ctx, cadenya.UploadNewParams{
  	WorkspaceID: cadenya.String(workspaceID),
  	Metadata:    shared.CreateResourceMetadataParam{Name: "policy handbook"},
  	Spec: cadenya.UploadSpecParam{
  		Filename:    "handbook.pdf",
  		ContentType: "application/pdf",
  		SizeBytes:   "4194304",
  	},
  })

  fmt.Println(upload.Info.UploadURL) // a signed storage URL
  fmt.Println(upload.Info.Status)    // UPLOAD_STATUS_PENDING
  ```

  ```ruby Ruby theme={null}
  upload = cadenya.uploads.create(
    workspace_id: workspace_id,
    metadata: {name: "policy handbook"},
    spec: {filename: "handbook.pdf", contentType: "application/pdf", sizeBytes: "4194304"}
  )

  puts upload.info.upload_url # a signed storage URL
  puts upload.info.status     # UPLOAD_STATUS_PENDING
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.cadenya.com/v1/workspaces/${WORKSPACE_ID}/uploads" \
    -H "Authorization: Bearer ${CADENYA_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{
          "metadata": { "name": "policy handbook" },
          "spec": { "filename": "handbook.pdf", "contentType": "application/pdf", "sizeBytes": "4194304" }
        }'
  ```
</CodeGroup>

`filename`, `contentType`, and `sizeBytes` are all required. `sizeBytes` is a string, because it is a 64-bit integer.

## The three steps

**1. Create the upload.** The response carries `info.uploadUrl`, a short-lived signed URL, and `info.uploadUrlExpiresAt`. The status is `UPLOAD_STATUS_PENDING`: the row exists, the bytes do not.

**2. PUT the bytes to that URL.** Upload straight to storage, not through the Cadenya API. Match the `Content-Type` you declared.

```bash theme={null}
curl -X PUT "${UPLOAD_URL}" \
  -H "Content-Type: application/pdf" \
  --data-binary @handbook.pdf
```

The URL points at object storage, so this call never touches Cadenya. When it succeeds, the upload moves to `UPLOAD_STATUS_COMPLETE`.

**3. Reference the `upload_id`.** Hand the ID to the resource that needs the payload, such as a [memory entry](/docs/api-reference/memoryservice/create-a-new-memory-entry) too large to inline:

```typescript theme={null}
await client.memoryLayers.entries.create(layerId, {
  workspaceId,
  metadata: { name: 'handbook' },
  spec: { type: 'uploadId', key: 'docs/handbook', uploadId: upload.metadata.id },
});
```

## You cannot reference an upload before its bytes land

This is the guard to code around. Referencing an upload still in `UPLOAD_STATUS_PENDING` is a `400`, with the reason spelled out:

```
POST .../entries  { uploadId: "<a pending upload>" }
  → 400  upload upload_01KX1VW... has status "pending", expected "complete"
```

So the order is fixed: create, PUT, then reference. A reference that fails with this message means the PUT has not finished, or never happened. Poll `GET /uploads/{id}` for `UPLOAD_STATUS_COMPLETE` before you use the ID if your upload and reference are decoupled.

## The status lifecycle

| Status                   | Meaning                                                       |
| ------------------------ | ------------------------------------------------------------- |
| `UPLOAD_STATUS_PENDING`  | Created. The signed URL is live; the bytes are not there yet. |
| `UPLOAD_STATUS_COMPLETE` | Bytes uploaded. The ID is ready to reference.                 |
| `UPLOAD_STATUS_CONSUMED` | The upload has been attached to a resource.                   |
| `UPLOAD_STATUS_EXPIRED`  | The signed URL lapsed before the bytes arrived.               |

An `EXPIRED` upload cannot be revived. Create a new one and PUT again. `uploadUrlExpiresAt` on the create response tells you how long you have.

## Related

<CardGroup cols={2}>
  <Card title="Get an upload" icon="magnifying-glass" href="/docs/api-reference/uploadservice/get-an-upload-by-id">
    Poll for `UPLOAD_STATUS_COMPLETE` before referencing.
  </Card>

  <Card title="Create a memory entry" icon="key" href="/docs/api-reference/memoryservice/create-a-new-memory-entry">
    `uploadId` for an entry too big to inline.
  </Card>

  <Card title="Set tool call content" icon="reply" href="/docs/api-reference/objectiveservice/set-a-bare-tool-calls-content">
    Large tool results follow the same big-payload pattern.
  </Card>

  <Card title="Create an objective" icon="bullseye" href="/docs/api-reference/objectiveservice/create-a-new-objective">
    Where large input data can reference an upload.
  </Card>
</CardGroup>


## OpenAPI

````yaml post /v1/workspaces/{workspaceId}/uploads
openapi: 3.1.0
info:
  title: Cadenya API
  description: API for the Cadenya Agent Runtime platform.
  version: '1.0'
servers:
  - url: https://api.cadenya.com
    description: Production server
security:
  - bearerAuth: []
tags:
  - name: AIProviderKeyService
  - name: APIKeyService
    description: |-
      Issue, rotate, disable, and revoke a workspace's API keys. Every key
       belongs to exactly one workspace; the system-managed global account key is
       managed via GlobalAPIKeyService instead.
  - name: AccountService
    description: >-
      Manage the authenticated account. Accounts are the top-level
      organizational
       unit and contain one or more workspaces.
  - name: AgentScheduleService
    description: >-
      Manage recurring schedules attached to agents. Schedules trigger
      objectives
       on a cadence defined by AgentScheduleSpec.Schedule.
  - name: AgentService
    description: >-
      Manage AI agents within a workspace. Agents define AI behavior and tool
      access.
  - name: AgentVariationService
    description: >-
      Manage variations of an agent and their tool, sub-agent, and memory layer
      assignments.
  - name: GlobalAPIKeyService
    description: |-
      Manage the account's system-provisioned global API key. The global key is
       the only key that spans every workspace; it is created by the system and
       cannot be deleted, so the surface is retrieve, rotate, and the
       disable/enable kill switch.
  - name: MemoryService
    description: >-
      Manage memory layers and their entries. Layers are named containers that
      can
       be composed into an objective's memory cascade; entries are the keyed values
       within a layer. System-managed layers (e.g., episodic layers created by the
       runtime) cannot be mutated through this API.
  - name: ModelService
    description: |-
      Manage LLM models available to a workspace. Models represent provider and
       family pairs (e.g., "anthropic/claude-sonnet-4.6"). Workspaces are seeded
       with the supported models and you can enable or disable each one.
  - name: ObjectiveEventStreamsService
  - name: ObjectiveService
  - name: ProfilesService
    description: |-
      Operations on profiles, the account-level principals (users, API keys,
       system) that authenticate against the API.
  - name: SearchService
  - name: TenantService
    description: >-
      Read and erase tenants and the subjects under them. Tenants and subjects
      are
       created by assertion — on objective creation or widget session mint — never
       directly, so this service has no create or update: it exists to enumerate what
       assertions have produced, and to destroy it on request.
  - name: ToolService
    description: >-
      Manage tool sets and the tools they contain. Tool sets group related
      tools,
       and tools define specific capabilities available to agents.

       When a tool set is managed, only API key actors can modify its tools; human
       (profile) actors cannot.
  - name: UploadService
    description: |-
      Issue short-lived presigned URLs for direct client-to-object-storage
       uploads. Created uploads can be referenced by id when creating or updating
       resources that accept binary content (e.g., MemoryEntry).
  - name: WidgetService
    description: |-
      Manage embeddable chat widgets. A widget binds an agent to a globally
       unique hostname with a per-widget origin allowlist; browsers reach it with
       session tokens minted via WidgetSessionService.
  - name: WidgetSessionService
    description: >-
      Mint and manage widget sessions. Session creation is server-to-server
      only:
       the customer's backend authenticates its visitor, asserts tenant/subject
       context, attaches any per-visitor secrets, and receives a short-lived
       bearer token the browser uses against the widget host.
  - name: WorkspaceAdminService
    description: >-
      Administer workspaces across the account: create and archive workspaces
      and
       manage their membership. These operations are account-scoped and require the
       admin role (a token whose profile holds the WorkOS admin role); they live
       under /v1/account/workspaces rather than the workspace-scoped /v1/workspaces
       tree so an admin can manage any workspace in the account, including ones they
       are not themselves a member of.
  - name: WorkspaceSecretService
  - name: WorkspaceService
    description: |-
      Manage workspaces within an account. Workspaces provide organizational
       grouping and isolation for resources such as agents, tools, and API keys.

       This is the workspace-scoped, end-user surface. Administrative operations
       (create / archive workspaces, manage members) live in WorkspaceAdminService
       under /v1/account/workspaces and require the admin role.
paths:
  /v1/workspaces/{workspaceId}/uploads:
    post:
      tags:
        - UploadService
        - Uploads
      summary: Create an upload
      description: >-
        Issues a short-lived presigned URL for direct upload to object storage.
        The returned id is used to reference the upload from resources that
        accept binary content.
      operationId: UploadService_CreateUpload
      parameters:
        - name: workspaceId
          in: path
          description: Workspace ID.
          required: true
          schema:
            type: string
            example: workspace_01HXKD2E5NQM3T9AYWCF133E3Q
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUploadRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Upload'
        default:
          description: Default error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
      x-codeSamples:
        - lang: JavaScript
          source: |-
            import Cadenya from '@cadenya/cadenya';

            const client = new Cadenya({
              apiKey: process.env['CADENYA_API_KEY'], // This is the default and can be omitted
            });

            const upload = await client.uploads.create({
              workspaceId: 'workspace_01HXKD2E5NQM3T9AYWCF133E3Q',
              metadata: { name: 'name' },
              spec: {
                contentType: 'contentType',
                filename: 'filename',
                sizeBytes: 'sizeBytes',
              },
            });

            console.log(upload.info);
        - lang: Python
          source: |-
            import os
            from cadenya import Cadenya

            client = Cadenya(
                api_key=os.environ.get("CADENYA_API_KEY"),  # This is the default and can be omitted
            )
            upload = client.uploads.create(
                workspace_id="workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
                metadata={
                    "name": "name"
                },
                spec={
                    "content_type": "contentType",
                    "filename": "filename",
                    "size_bytes": "sizeBytes",
                },
            )
            print(upload.info)
        - lang: Go
          source: "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"go.cadenya.com/cadenya-go\"\n\t\"go.cadenya.com/cadenya-go/option\"\n\t\"go.cadenya.com/cadenya-go/shared\"\n)\n\nfunc main() {\n\tclient := cadenya.NewClient(\n\t\toption.WithAPIKey(\"My API Key\"),\n\t)\n\tupload, err := client.Uploads.New(context.TODO(), cadenya.UploadNewParams{\n\t\tWorkspaceID: cadenya.String(\"workspace_01HXKD2E5NQM3T9AYWCF133E3Q\"),\n\t\tMetadata: shared.CreateResourceMetadataParam{\n\t\t\tName: \"name\",\n\t\t},\n\t\tSpec: cadenya.UploadSpecParam{\n\t\t\tContentType: \"contentType\",\n\t\t\tFilename:    \"filename\",\n\t\t\tSizeBytes:   \"sizeBytes\",\n\t\t},\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf(\"%+v\\n\", upload.Info)\n}\n"
        - lang: Ruby
          source: |-
            require "cadenya"

            cadenya = Cadenya::Client.new(api_key: "My API Key")

            upload = cadenya.uploads.create(
              workspace_id: "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
              metadata: {name: "name"},
              spec: {contentType: "contentType", filename: "filename", sizeBytes: "sizeBytes"}
            )

            puts(upload)
        - lang: CLI
          source: |-
            cadenya uploads create \
              --api-key 'My API Key' \
              --workspace-id workspace_01HXKD2E5NQM3T9AYWCF133E3Q \
              --metadata '{name: name}' \
              --spec '{contentType: contentType, filename: filename, sizeBytes: sizeBytes}'
components:
  schemas:
    CreateUploadRequest:
      required:
        - metadata
        - spec
      type: object
      properties:
        workspaceId:
          readOnly: true
          example: workspace_01HXKD2E5NQM3T9AYWCF133E3Q
          type: string
          description: Workspace ID.
        metadata:
          $ref: '#/components/schemas/CreateResourceMetadata'
        spec:
          $ref: '#/components/schemas/UploadSpec'
    Upload:
      required:
        - metadata
        - spec
        - info
      type: object
      properties:
        metadata:
          $ref: '#/components/schemas/ResourceMetadata'
        spec:
          $ref: '#/components/schemas/UploadSpec'
        info:
          $ref: '#/components/schemas/UploadInfo'
      description: >-
        A handle representing a single file upload flow. Clients call
        CreateUpload
         to receive a short-lived presigned URL, PUT the file directly to object
         storage, then reference the upload by id when creating or updating
         resources that accept binary content.

         Uploads are one-shot: once consumed by a creating or updating resource the
         upload transitions to UPLOAD_STATUS_CONSUMED and cannot be reused. Unused
         uploads expire and are garbage-collected.
    Status:
      type: object
      properties:
        code:
          type: integer
          description: >-
            The status code, which should be an enum value of
            [google.rpc.Code][google.rpc.Code].
          format: int32
        message:
          type: string
          description: >-
            A developer-facing error message, which should be in English. Any
            user-facing error message should be localized and sent in the
            [google.rpc.Status.details][google.rpc.Status.details] field, or
            localized by the client.
        details:
          type: array
          items:
            $ref: '#/components/schemas/GoogleProtobufAny'
          description: >-
            A list of messages that carry the error details.  There is a common
            set of message types for APIs to use.
      description: >-
        The `Status` type defines a logical error model that is suitable for
        different programming environments, including REST APIs and RPC APIs. It
        is used by [gRPC](https://github.com/grpc). Each `Status` message
        contains three pieces of data: error code, error message, and error
        details. You can find out more about this error model and how to work
        with it in the [API Design
        Guide](https://cloud.google.com/apis/design/errors).
    CreateResourceMetadata:
      required:
        - name
      type: object
      properties:
        name:
          type: string
          description: >-
            Human-readable name for the resource (e.g., "Customer Support
            Agent", "Email Tool")
        externalId:
          type: string
          description: >-
            External ID for the resource (e.g., a workflow ID from an external
            system)
        labels:
          type: object
          additionalProperties:
            type: string
          description: |-
            Key-value pairs for categorization and filtering. Values are 0-63
             alphanumeric characters with "-", "_", or "." allowed between; keys
             follow the same shape and additionally accept an optional DNS-subdomain
             prefix (e.g. "cadenya.com/") of at most 253 characters.
             Examples: {"environment": "production", "team": "platform", "version": "v2"}
      description: |-
        CreateResourceMetadata contains the user-provided fields for creating
         a workspace-scoped resource. Read-only fields (id, account_id, workspace_id, profile_id,
         created_at) are excluded since they are set by the server.
    UploadSpec:
      required:
        - filename
        - contentType
        - sizeBytes
      type: object
      properties:
        filename:
          type: string
          description: |-
            Client-supplied filename. Used for audit and display only; does not
             control the object's storage path.
        contentType:
          type: string
          description: >-
            MIME type the client will send. Baked into the presigned URL's
            signature
             — the PUT must match exactly or object storage will reject it.
        sizeBytes:
          type: string
          description: >-
            Expected size of the upload in bytes. Baked into the presigned URL
            as a
             Content-Length constraint.
    ResourceMetadata:
      required:
        - id
        - accountId
        - workspaceId
        - name
        - profileId
        - createdAt
      type: object
      properties:
        id:
          readOnly: true
          type: string
          description: >-
            Unique identifier for the resource (prefixed ULID, e.g.,
            "agent_01HXK...")
        accountId:
          readOnly: true
          example: account_01HXKD2E5NQM3T9AYWCFTJHJVF
          type: string
          description: >-
            Account this resource belongs to for multi-tenant isolation
            (prefixed ULID)
        workspaceId:
          readOnly: true
          example: workspace_01HXKD2E5NQM3T9AYWCF133E3Q
          type: string
          description: >-
            Workspace this resource belongs to for organizational grouping
            (prefixed ULID)
        name:
          type: string
          description: >-
            Human-readable name for the resource (e.g., "Customer Support
            Agent", "Email Tool")
             Required for resources that users interact with directly
        externalId:
          type: string
          description: >-
            External ID for the resource (e.g., a workflow ID from an external
            system)
        labels:
          type: object
          additionalProperties:
            type: string
          description: |-
            Key-value pairs for categorization and filtering. Values are 0-63
             alphanumeric characters with "-", "_", or "." allowed between; keys
             follow the same shape and additionally accept an optional DNS-subdomain
             prefix (e.g. "cadenya.com/") of at most 253 characters.
             Examples: {"environment": "production", "team": "platform", "version": "v2"}
        profileId:
          readOnly: true
          example: profile_01HXKD2E5NQM3T9AYWCFS0AP08
          type: string
          description: ID of the actor (user or service account) that created this resource
        createdAt:
          readOnly: true
          type: string
          description: Timestamp when this resource was created
          format: date-time
        updatedAt:
          readOnly: true
          type: string
          description: Timestamp when this resource was last updated
          format: date-time
      description: >-
        Standard metadata for persistent, named resources (e.g., agents, tools,
        prompts)
    UploadInfo:
      type: object
      properties:
        uploadUrl:
          readOnly: true
          type: string
          description: |-
            Presigned PUT URL. Short-lived. The client must PUT with the exact
             Content-Type declared in the spec, and the body length must match
             size_bytes.
        uploadUrlExpiresAt:
          readOnly: true
          type: string
          description: Absolute time at which upload_url stops working.
          format: date-time
        status:
          readOnly: true
          enum:
            - UPLOAD_STATUS_UNSPECIFIED
            - UPLOAD_STATUS_PENDING
            - UPLOAD_STATUS_COMPLETE
            - UPLOAD_STATUS_CONSUMED
            - UPLOAD_STATUS_EXPIRED
          type: string
          description: >-
            Lifecycle state. Transitions PENDING → COMPLETE (storage confirms
            the
             object exists) → CONSUMED (a resource referenced this upload), or
             → EXPIRED (URL elapsed without a PUT).
          format: enum
        createdBy:
          $ref: '#/components/schemas/Profile'
    GoogleProtobufAny:
      type: object
      properties:
        '@type':
          type: string
          description: The type of the serialized message.
      additionalProperties: true
      description: >-
        Contains an arbitrary serialized message along with a @type that
        describes the type of the serialized message.
    Profile:
      required:
        - metadata
        - spec
      type: object
      properties:
        metadata:
          $ref: '#/components/schemas/AccountResourceMetadata'
        spec:
          $ref: '#/components/schemas/ProfileSpec'
      description: |-
        A profile identifies a user or non-human principal (such as an API key)
         at the account level. Profiles are account-scoped and can be granted access
         to multiple workspaces.
    AccountResourceMetadata:
      required:
        - id
        - accountId
        - name
        - profileId
      type: object
      properties:
        id:
          readOnly: true
          type: string
          description: >-
            Unique identifier for the resource (prefixed ULID, e.g.,
            "apikey_01HXK...")
        accountId:
          readOnly: true
          example: account_01HXKD2E5NQM3T9AYWCFTJHJVF
          type: string
          description: >-
            Account this resource belongs to for multi-tenant isolation
            (prefixed ULID)
        name:
          type: string
          description: >-
            Human-readable name for the resource (e.g., "Customer Support
            Agent", "Email Tool")
             Required for resources that users interact with directly
        externalId:
          type: string
          description: >-
            External ID for the resource (e.g., a workflow ID from an external
            system)
        labels:
          type: object
          additionalProperties:
            type: string
          description: |-
            Key-value pairs for categorization and filtering. Values are 0-63
             alphanumeric characters with "-", "_", or "." allowed between; keys
             follow the same shape and additionally accept an optional DNS-subdomain
             prefix (e.g. "cadenya.com/") of at most 253 characters.
             Examples: {"environment": "production", "team": "platform", "version": "v2"}
        profileId:
          readOnly: true
          example: profile_01HXKD2E5NQM3T9AYWCFS0AP08
          type: string
        createdAt:
          readOnly: true
          type: string
          format: date-time
      description: >-
        AccountResourceMetadata is used to represent a resource that is
        associated to an account but not to a workspace.
    ProfileSpec:
      required:
        - type
      type: object
      properties:
        email:
          type: string
          description: >-
            Email address of the profile. Required and unique within an account
            for
             user profiles.
        name:
          type: string
          description: Display name (e.g., "Bobby Tables").
        type:
          enum:
            - PROFILE_TYPE_UNSPECIFIED
            - PROFILE_TYPE_USER
            - PROFILE_TYPE_API_KEY
            - PROFILE_TYPE_SYSTEM
          type: string
          description: >-
            Whether this profile represents a human user, an API key, or a
            system
             principal.
          format: enum
      description: Configuration for a profile.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````