---
title: "Create a file"
description: "Add a file to the library, as the Files page's Add does. type is file (a stored upload), link (an absolute url, such as https://…, to an external page or download) or licence_key (a pool of keys). A file answers with upl"
url: https://docs.filemonk.io/v2/api-reference/assets/create-a-file/
api: "API v2 reference"
method: POST
path: "/assets"
---

# Create a file

`POST https://app.filemonk.io/api/v2/external/assets`

Add a file to the library, as the Files page's Add does. type is file (a stored upload), link (an absolute url, such as https://…, to an external page or download) or licence_key (a pool of keys). A file answers with upload: {url, method, headers, expires_at}; PUT the file's bytes there within the hour, nothing else is needed. An AI assistant that can't send the bytes itself (in a chat) should ask the merchant to upload the file on the Files page instead. watermark.enabled applies to PDFs; licence_keys.autogenerate mints keys on demand, licence_keys.tags names the pools the file draws from.

**Note:** A stored file counts against the plan's file and storage quota from this call (402-style quota errors come back as 422 with code). Nothing is linked to a product until [`POST /digital_products/:id/files/attach`](https://docs.filemonk.io/v2/api-reference/digital-products/attach-files-to-a-digital-product/).

## Request body

- `type` (string, required): e.g. `file`
- `name` (string): e.g. `Beginner's Guide`
- `url` (any)
- `file` (object)
  - `filename` (string): e.g. `guide.pdf`
  - `byte_size` (integer): e.g. `1048576`
  - `content_type` (string): e.g. `application/pdf`
- `watermark` (object)
  - `enabled` (boolean): e.g. `true`
- `licence_keys` (any)

Rate limit: this call costs 2 points ([rate limits](https://docs.filemonk.io/v2/errors-and-rate-limits/)).

## Examples

### Upload a PDF with a watermark

Answers with an upload URL; PUT the file's bytes there within the hour.

```sh
curl -X POST "https://app.filemonk.io/api/v2/external/assets" \
  -H "X-Auth-Token: $FILEMONK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "file",
    "name": "Beginner'\''s Guide",
    "file": {
      "filename": "guide.pdf",
      "byte_size": 1048576,
      "content_type": "application/pdf"
    },
    "watermark": {
      "enabled": true
    }
  }'
```

Response:

```json
{
  "id": "e2b7727e-45ad-42b7-b842-cc4615205b10",
  "type": "file",
  "name": "guide.pdf",
  "filename": "guide.pdf",
  "extension": "pdf",
  "content_type": "application/pdf",
  "byte_size": 1048576,
  "url": null,
  "url_expires_at": null,
  "watermark": {
    "enabled": true
  },
  "licence_keys": {
    "autogenerate": false,
    "tags": []
  },
  "usage": {
    "digital_products_count": 0,
    "orders_count": 0
  },
  "digital_products": [],
  "recent_orders": [],
  "created_at": "2026-10-30T16:49:38Z",
  "updated_at": "2026-10-30T16:49:38Z",
  "upload": {
    "url": "https://files.filemonk.io/uploads/5f3e9c1a/guide.pdf?X-Amz-Expires=3600&X-Amz-Signature=3f6c…",
    "method": "PUT",
    "headers": {
      "Content-Type": "application/pdf"
    },
    "expires_at": "2026-10-31T00:10:00Z"
  }
}
```

### Add a link to an external download

Nothing is uploaded: buyers get the link.

```sh
curl -X POST "https://app.filemonk.io/api/v2/external/assets" \
  -H "X-Auth-Token: $FILEMONK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "link",
    "name": "Watercolour course videos",
    "url": "https://cdn.example.com/watercolour-course-videos.zip"
  }'
```

Response:

```json
{
  "id": "e2b7727e-45ad-42b7-b842-cc4615205b10",
  "type": "link",
  "name": "Watercolour course videos",
  "filename": "watercolour-course-videos.zip",
  "extension": "zip",
  "content_type": "application/zip",
  "byte_size": 0,
  "url": "https://cdn.example.com/watercolour-course-videos.zip",
  "url_expires_at": null,
  "watermark": {
    "enabled": false
  },
  "licence_keys": {
    "autogenerate": false,
    "tags": []
  },
  "usage": {
    "digital_products_count": 0,
    "orders_count": 0
  },
  "digital_products": [],
  "recent_orders": [],
  "created_at": "2026-10-30T16:49:38Z",
  "updated_at": "2026-10-30T16:49:38Z",
  "upload": {
    "url": "https://files.filemonk.io/uploads/5f3e9c1a/guide.pdf?X-Amz-Expires=3600&X-Amz-Signature=3f6c…",
    "method": "PUT",
    "headers": {
      "Content-Type": "application/pdf"
    },
    "expires_at": "2026-10-31T00:10:00Z"
  }
}
```

## 200

Success: the resource already existed; here it is.

## 201

Created: the new resource.

```json
{
  "id": "e2b7727e-45ad-42b7-b842-cc4615205b10",
  "type": "file",
  "name": "guide.pdf",
  "filename": "guide.pdf",
  "extension": "pdf",
  "content_type": "application/pdf",
  "byte_size": 1048576,
  "url": null,
  "url_expires_at": null,
  "watermark": {
    "enabled": true
  },
  "licence_keys": {
    "autogenerate": false,
    "tags": []
  },
  "usage": {
    "digital_products_count": 0,
    "orders_count": 0
  },
  "digital_products": [],
  "recent_orders": [],
  "created_at": "2026-10-30T16:49:38Z",
  "updated_at": "2026-10-30T16:49:38Z",
  "upload": {
    "url": "https://files.filemonk.io/uploads/5f3e9c1a/guide.pdf?X-Amz-Expires=3600&X-Amz-Signature=3f6c…",
    "method": "PUT",
    "headers": {
      "Content-Type": "application/pdf"
    },
    "expires_at": "2026-10-31T00:10:00Z"
  }
}
```

## 401

Missing or invalid API key.

```json
{
  "errors": [
    "Invalid API key"
  ]
}
```

## 422

The request failed validation, or sent a key the action does not take (`code: &quot;unknown_parameters&quot;` with `unknown` and `accepted`). Nothing was saved.

```json
{
  "errors": [
    "access.download_limit_count must be an integer of at least 0"
  ],
  "field_errors": {
    "access.download_limit_count": [
      "must be an integer of at least 0"
    ]
  }
}
```

## 429

Rate limited. Cost-weighted per API key, shared with API v1: a read costs 1 point, a write 2, 40 points per fixed 60-second window. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time the window ends); a 429 adds `Retry-After` (seconds), `retry_after`, `retryable: true` and a `next_step` in the body. A 429 did nothing: wait `retry_after` seconds, then send the same request again. The budget is per key, so every tool or script using the key shares it; prefer one list call with `per_page` over many single reads.

```json
{
  "errors": [
    "You have exceeded the rate limit of 40 points per 60 seconds. Read requests cost 1 point(s) and write requests cost 2 point(s). Please retry after 12 second(s)."
  ],
  "code": "rate_limited",
  "retry_after": 12,
  "error": "Rate limit exceeded",
  "message": "You have exceeded the rate limit of 40 points per 60 seconds. Read requests cost 1 point(s) and write requests cost 2 point(s). Please retry after 12 second(s)."
}
```

