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

# Upload File

> Upload images and videos to Genviral CDN for use in TikTok slideshows, Instagram carousels, YouTube Shorts, Pinterest pins, LinkedIn posts, and Facebook content. Supports programmatic uploads from OpenClaw agents and automation pipelines.

Upload media files directly to Genviral's CDN. The returned `url` can then be used when creating posts via `/posts` or attaching images to packs via `/packs/{packId}/images`. The stored path is derived from key scope (`partner-api/workspaces/{workspaceId}` or `partner-api/users/{ownerUserId}`).

## How It Works

1. Call this endpoint with the file's content type
2. Receive a presigned `uploadUrl` and the final `url` (CDN URL)
3. Upload your file directly to the `uploadUrl` using a PUT request
4. Use the `url` in post creation requests or pack-image attachment requests

## Body Parameters

<ParamField body="contentType" type="string" required>
  MIME type of the file. Supported types:

  * **Images**: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/heif`
  * **Videos**: `video/mp4`, `video/quicktime`, `video/x-msvideo`, `video/webm`, `video/x-m4v`
</ParamField>

<ParamField body="filename" type="string">
  Original filename for reference (optional). Used for display purposes only.
</ParamField>

<ParamField body="duration_sec" type="number">
  Optional video duration in seconds. Also accepts `duration_seconds`, `duration`, `durationSec`,
  or `video_duration_sec`. Stored with the CDN file record so `/posts` can hydrate validation
  metadata when you use the returned `url`.
</ParamField>

<ParamField body="bytes" type="number">
  Optional file size in bytes. Also accepts `size`.
</ParamField>

## Response

Successful requests return `201` with:

* `uploadUrl` - Presigned URL to upload your file (expires in 10 minutes)
* `url` - CDN URL where your file will be accessible after upload
* `contentType` - The content type you specified
* `expiresIn` - Seconds until the upload URL expires (600)

## Using With Packs

If your goal is to add a local file to a pack:

1. Call this endpoint and capture `data.uploadUrl` + `data.url`.
2. Upload your bytes to `data.uploadUrl` with `PUT`.
3. Call [Add Pack Image](/api-reference/add-pack-image) with `image_url = data.url`.

## Examples

### Upload a video

<RequestExample>
  ```javascript theme={null}
  import fetch from "node-fetch";

  // Step 1: Get upload URL
  const response = await fetch("https://www.genviral.io/api/partner/v1/files", {
    method: "POST",
    headers: {
      Authorization: "Bearer <token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      contentType: "video/mp4",
      filename: "my-video.mp4",
      duration_sec: 42,
      bytes: 8_000_000,
    }),
  });

  const { data } = await response.json();
  // data.uploadUrl = presigned S3 URL
  // data.url = https://cdn.vireel.io/partner-api/workspaces/{workspaceId}/... (or /users/{ownerUserId}/...)

  // Step 2: Upload file to presigned URL
  const fileBuffer = fs.readFileSync("./my-video.mp4");
  await fetch(data.uploadUrl, {
    method: "PUT",
    headers: {
      "Content-Type": "video/mp4",
    },
    body: fileBuffer,
  });

  // Step 3: Use the CDN URL in your post
  await fetch("https://www.genviral.io/api/partner/v1/posts", {
    method: "POST",
    headers: {
      Authorization: "Bearer <token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      caption: "Check this out!",
      media: {
        type: "video",
        url: data.url, // Use the CDN URL
      },
      accounts: [{ id: "account-id" }],
    }),
  });
  ```
</RequestExample>

```bash theme={null}
# Step 1: Get upload URL
curl --request POST \
  --url https://www.genviral.io/api/partner/v1/files \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "contentType": "video/mp4",
    "filename": "my-video.mp4",
    "duration_sec": 42,
    "bytes": 8000000
  }'

# Step 2: Upload to presigned URL (use uploadUrl from response)
curl --request PUT \
  --url "<uploadUrl from step 1>" \
  --header 'Content-Type: video/mp4' \
  --data-binary @my-video.mp4

# Step 3: Use url in your post request
```

<ResponseExample>
  ```json Response theme={null}
  {
    "ok": true,
    "code": 201,
    "message": "Upload URL generated",
    "data": {
      "uploadUrl": "https://storage.example.com/presigned-url...",
      "url": "https://cdn.vireel.io/partner-api/workspaces/workspace_123/abc123.mp4",
      "contentType": "video/mp4",
      "expiresIn": 600
    }
  }
  ```
</ResponseExample>

## Error Responses

* `400 invalid_json` - Request body is not valid JSON
* `422 invalid_payload` - Invalid content type or missing required fields
* `401` - authentication failed (missing/invalid/revoked token)
* `402 subscription_required` - active Creator/Professional/Business plan required
* `403 tier_not_allowed` - Scheduler tier cannot use Partner API
* `500 create_failed` - Failed to initialize upload (retry)

<Note>
  The presigned upload URL expires after 10 minutes. If it expires before you upload,
  simply request a new one.
</Note>

<Tip>
  `POST /files` reserves the CDN URL and upload slot. The file is ready for use
  only after your `PUT` to `uploadUrl` succeeds (HTTP 200/204). If needed,
  verify reachability with a `HEAD`/`GET` against `data.url` before creating a
  post.
</Tip>
