---
title: "Upload brand files"
description: "Add an icon, logos, banner, social image, favicon or collection file to a brand in two steps."
---

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

# Upload brand files

Uploads take two calls, so file bytes never pass through the API. Both need the `brands:files` scope, and the token's creator must be an owner or admin of the workspace named in `X-Workspace-Key`.

## 1. Start the upload

`POST /brands/{brand}/files` with the place the file is for.

```bash
curl -X POST -H "Authorization: Bearer $TARKLE_TOKEN" -H "X-Workspace-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"slot": "icon", "size": 18204, "filename": "icon.png"}' \
  https://tarkle.com/api/v1/brands/acme-bank/files
```

| `slot` | What it does |
| --- | --- |
| `icon`, `main_logo`, `banner`, `social_image`, `favicon` | Replaces the brand's current one. |
| `logo` | Adds an extra logo. |
| `asset` | Adds a file to a collection. |

`size` and `filename` are optional. They let a file that is too large or the wrong type be refused before you upload it.

The answer (`201`) holds:

- `file_id`, which you use in step 3. It only works for this brand and this place.
- `upload.url` and `upload.method` (`PUT`). The address expires after `upload.expires_in` seconds (3,600).
- `max_bytes` and `extensions` (the allowed file types, or `null` for any type).
- `complete_url`.

## 2. Send the file

`PUT` the file bytes to `upload.url`. Send the bytes as the body, with no extra authorization header.

```bash
curl -X PUT --data-binary @icon.png "$UPLOAD_URL"
```

## 3. Complete the upload

`POST /brands/{brand}/files/{file_id}/complete`. Tarkle checks what was stored and attaches it.

```bash
curl -X POST -H "Authorization: Bearer $TARKLE_TOKEN" -H "X-Workspace-Key: $KEY" \
  https://tarkle.com/api/v1/brands/acme-bank/files/$FILE_ID/complete
```

The checks are the ones of the brand editor: the file's real type (its first bytes, not its name), its size and that it arrived.

| Place | Types | Largest |
| --- | --- | --- |
| `icon`, `main_logo`, `logo` | PNG, JPG, WebP, SVG | 10 MB |
| `banner`, `social_image` | PNG, JPG, WebP | 10 MB |
| `favicon` | PNG, JPG, WebP, SVG, ICO | 2 MB |
| `asset` | Any | 100 MB |

For `logo`, the body may hold `name`, `type` (`primary`, `secondary`, `monochrome`, `reversed`, `icon`, `favicon`, `square` or `social media`) and `usage` (`digital`, `print` or `all purpose`). For `asset`, send `collection_id`, or `collection_name` to start a new collection, plus an optional `name` and `type`. The other places take no body.

The answer (`200`) holds `slot`, `file_id`, `size` and `already_attached`. Completing the same file again answers the same with `already_attached: true` and changes nothing. A replaced icon, logo, banner, social image or favicon is removed from storage unless the brand still uses it.

## Errors

| Answer | Meaning |
| --- | --- |
| `400 invalid_request` | The body, the slot or the `file_id` is wrong, or the `file_id` was made for another brand or place. |
| `409 file_not_uploaded` | Nothing was found at the upload address. `PUT` the file, then complete again. |
| `413 file_too_large` | The declared `size` is over the limit for that place. |
| `422 invalid_file` | The stored file is too large, empty or not a real file of an allowed type. The file is removed. |
| `409 conflict` | A brand holds 20 extra logos, 30 collections or 200 files in a collection. Or the brand still has images from the older editor, which blocks `asset` uploads: open the brand in the app once to move them into collections. |

An upload is recorded in the Activity as `brand.updated` with the place, and `brand.updated` [webhooks](/docs/api/webhooks) fire once.

The API does not remove files, and it does not upload team photos, guideline images or fonts.

Source: https://tarkle.com/docs/api/brands/files/index.mdx
