# Per-User Brand Assets

> Scope colors, images, fonts, videos and audio to a single embed user

- **URL**: https://orshot.com/docs/orshot-embed/per-user-brand-assets

---

Give each of your embed users their own colors, images, fonts, videos and audio, seeded from your backend. This builds on [Per-User Data](https://orshot.com/docs/orshot-embed/per-user-data), which needs to be enabled on the embed first.

## Overview

Every brand asset belongs to a workspace. When you also pass an embed user, the asset belongs to that user's private library instead, and only that user sees it in the editor.

This is what you want when one embed serves many brands, tenants or customers and each needs its own brand space. Push a brand's palette, logos and typeface from your backend, and the next time that brand opens the embed the assets are already there.

Pass both parameters together on any brand assets endpoint:

| Parameter     | Type   | Description                                                       |
| ------------- | ------ | ----------------------------------------------------------------- |
| `embedId`     | String | Your embed instance ID, from the embed settings in your dashboard |
| `embedUserId` | String | The same `userId` your app passes to the embed URL                |

They work as query parameters on every endpoint and verb. Write endpoints also accept them in the JSON body as `embedUserId` (or `embed_user_id`), matching the Studio Templates API.

Leave both out and nothing changes: writes go to the shared workspace library
and reads return the whole workspace, exactly as before.

## Seeding a Brand

```js
const API = "https://api.orshot.com/v1/brand-assets";
const auth = {
  "Content-Type": "application/json",
  Authorization: "Bearer <ORSHOT_API_KEY>",
};
const scope = "embedId=YOUR_EMBED_ID&embedUserId=brand_123";

// A color, straight into that brand's palette
await fetch(`${API}/colors/add?${scope}`, {
  method: "POST",
  headers: auth,
  body: JSON.stringify({ type: "hex", value: "#FF5733", tags: ["primary"] }),
});

// A logo, into that brand's image library
await fetch(`${API}/images/add?${scope}`, {
  method: "POST",
  headers: auth,
  body: JSON.stringify({
    file: "https://cdn.example.com/brand-123/logo.png",
    fileName: "logo.png",
  }),
});

// A typeface, into that brand's font list
await fetch(`${API}/fonts/add?${scope}`, {
  method: "POST",
  headers: auth,
  body: JSON.stringify({
    file: "https://cdn.example.com/brand-123/Grotesk.woff2",
    name: "Brand Grotesk",
  }),
});
```

Reading back what one brand has:

```js
await fetch(`${API}/colors/get?${scope}`, {
  headers: { Authorization: "Bearer <ORSHOT_API_KEY>" },
});
```

## Supported Endpoints

`embedId` and `embedUserId` are accepted on every brand assets endpoint:

| Asset  | Endpoints                                                                 |
| ------ | ------------------------------------------------------------------------- |
| Images | `/images/get`, `/images/add`, `/images/confirm`, `/images/update/:id`, `/images/delete/:id` |
| Colors | `/colors/get`, `/colors/add`, `/colors/update/:id`, `/colors/delete/:id`  |
| Fonts  | `/fonts/get`, `/fonts/add`, `/fonts/update/:id`, `/fonts/delete/:id`      |
| Videos | `/videos/get`, `/videos/add`, `/videos/update/:id`, `/videos/delete/:id`  |
| Audio  | `/audio/get`, `/audio/add`, `/audio/update/:id`, `/audio/delete/:id`      |
| Search | `/search`                                                                 |

`/images/upload-url` takes no scope of its own. Pass the parameters to `/images/confirm` instead, which is where the asset is created.

## What Each User Sees

With [Per-User Data](https://orshot.com/docs/orshot-embed/per-user-data) enabled and a `userId` in the embed URL, the editor scopes most asset types to that user rather than merging them with the workspace library:

| Asset     | Workspace level        | That user's own |
| --------- | ---------------------- | --------------- |
| Templates | Visible\*              | Visible         |
| Fonts     | Visible                | Visible         |
| Images    | Hidden                 | Visible         |
| Videos    | Hidden                 | Visible         |
| Audio     | Hidden                 | Visible         |
| Colors    | Hidden                 | Visible         |

\*Workspace templates are hidden when **Can see studio templates** is turned off for the embed.

So for images, videos, audio and colors, seeding per user is the only way to put an asset in front of an end user on a per-user embed. Fonts are the exception: a workspace-level font reaches every user.

Rendering is unaffected by scope. A template that references a per-user font or image renders the same through the API regardless of which library the asset lives in.

## Notes

- Writes create the embed user if it does not exist yet, so you can seed a brand before it has ever opened the embed. This requires **Per-User Data & Templates** to be enabled on the embed, and it counts against your plan's embed user limit.
- Reads, tag updates and deletes never create a user. They resolve the ID and match rows, so an unknown `embedUserId` simply returns nothing.
- Deletes and tag updates are scoped too, so one user's ID can never touch another user's asset.
- Responses carry `embed_user_id` as a hashed internal ID (`eui_` plus 16 characters), so asset rows never expose your original `userId`. The original ID is kept on the embed user record itself, visible to you in your dashboard's user list. Passing either form back works.
- If your workspace has more than one embed, always send `embedId`. Without it a brand new user cannot be placed.
- Deleting an embed user from the dashboard either deletes their assets or releases them back to the workspace, depending on the option you choose.

## Rate Limits

- 30 requests per minute per endpoint