Freedam Picker Reference
The Freedam Picker lets people choose assets from a Freedam workspace inside another application: a CMS, an e-commerce back office, an internal tool. Your page calls open(), Freedam opens the picker in a window on the workspace host, over your page or inside it, the person searches and chooses, and your page receives one object per asset with ready-to-use URLs.
This page describes every option, event, error code and payload field of SDK version 1.2. The Freedam Picker quickstart has a complete example to copy, and the playground opens the picker with the options you choose and shows the code and the payload.
How it works
- Your page loads the picker SDK and calls
open()from a click handler. - The SDK opens the picker page of the workspace, for example
https://acme.freedam.io/picker/w5r8n2c4t7bd: in a popup by default, in a new tab, or in an iframe over your page or inside one of its elements (see Display modes). - Freedam checks that your page's origin is allowed for the integration, then shows the picker. A person who is not signed in signs in first (see Sign-in).
- The person chooses assets and inserts them. Freedam publishes them on the integration's publication channel, records the usage, and sends the assets to your page with
postMessage. - The picker closes and
open()resolves with the assets.
The URLs you receive belong to the publication channel: they are public, need no token, and keep working if the integration is later deleted. They stop working when the asset is unpublished from the channel or its usage rights expire.
Before you start
A workspace administrator creates an integration on the Picker integrations page and gives you:
- the workspace host, for example
acme.freedam.ioor your own domain for a self-hosted workspace; - the integration key, for example
w5r8n2c4t7bd; - confirmation that your page's origin is on the integration's list, for example
https://cms.example.com. An origin is a scheme, host and port, without a path. A pattern such ashttps://*.staging.example.comallows every subdomain.
The integration also decides which assets the picker shows, which metadata fields it sends and which image formats it offers.
Loading the SDK
Script tag
The workspace host serves the SDK. The v1 URL always serves the latest 1.x version:
<script src="https://acme.freedam.io/sdk/picker/v1/picker.js"></script>
The script adds window.Freedam.picker with open, FreedamPickerError and VERSION, and defines the <freedam-picker> element (see Web component). It keeps any window.Freedam object already on the page.
To pin an exact version and check its integrity, use the version's own URL with its integrity value, listed under Versions:
<script
src="https://acme.freedam.io/sdk/picker/1.2.0/picker.js"
integrity="sha384-NngTI5clKCwu+PbhM6M3EDuOWhcywXV3y8qOJ1DrH7cAs8HfCoJgSdeMCuO8rgo/"
crossorigin="anonymous"
></script>
The files of an exact version never change.
npm
npm install @freedam/picker
import { open, FreedamPickerError } from '@freedam/picker';
import type { PickerAsset } from '@freedam/picker';
The package has no runtime dependency and ships its TypeScript types. import '@freedam/picker/element' defines the <freedam-picker> element; it is the only module with a side effect. The script weighs a few kilobytes.
open(options)
open(options: PickerOptions): Promise<PickerAsset[] | null>
Call open() directly in a click (or key press) handler, with no await before it. Browsers block windows opened outside a user gesture; the SDK then reports popup_blocked.
While a picker opened by the page is still open, another call brings it to the front and returns the same promise.
Options
| Option | Type | Default | Description |
|---|---|---|---|
workspace |
string | required | Workspace host, without scheme or path: acme.freedam.io, dam.example.com:8443. The SDK always uses HTTPS. |
integration |
string | required | Integration key. |
publicKey |
string | none | Key of the integration's access without an account (fdpk_ followed by 32 letters and digits), shown in the integration's snippet when an administrator turns the access on. With it, people without a Freedam account can browse and insert the assets the integration offers to Public Portal guests. It is not a secret. Needs version 1.1. |
select |
'single' | 'multiple' |
'single' |
Whether the person chooses one asset or several. |
max |
number | none | With select: 'multiple', the most assets the person can choose: 1 to 50. |
q |
string | none | Initial search text, 500 characters at most. |
filters |
object | none | Initial filter tree, in the format of the filters parameter of GET /api/v1/assets (see Endpoints). It narrows the integration's own rules and never widens them. |
sort |
{ by, direction? } |
relevance or newest | by: created_at, updated_at, title, original_filename, size, relevance; direction: asc or desc. |
preselected |
string[] | none | GAIDs selected when the picker opens, to edit an earlier choice. GAIDs the person cannot see are ignored. |
types |
string[] | the integration's types | Asset types the picker offers: 'image', 'video', 'document'. They narrow the integration's own types and never widen them: a type the integration does not offer is ignored, and when it offers none of them the picker shows no asset. Since version 1.2. |
locale |
string | the person's language | en, fr, de, es, pt-BR, zh-CN. |
theme |
'light' | 'dark' | 'auto' |
the person's setting | Colour theme of the picker. |
mode |
'popup' | 'tab' | 'modal' | 'inline' |
'popup' |
How the picker opens: in a window, in a new tab, over your page or inside container (see Display modes). modal and inline need version 1.1, tab version 1.2. |
container |
HTMLElement | none | With mode: 'inline', and only then: the element of your page that receives the picker. Required in that mode. |
onReady |
() => void |
none | The picker is displayed and ready. Called once per opening. |
onSelect |
(assets) => void |
none | The person inserted assets. |
onCancel |
() => void |
none | The person cancelled or closed the picker without inserting. |
onError |
(error) => void |
none | An error occurred; see Errors. |
onClose |
() => void |
none | The picker is closed. Always the last call. |
Invalid options reject the promise with a FreedamPickerError whose code is invalid_options, before any window opens, and also call onError. The error message names the option.
The opening URL carries every option. Above 6,000 characters (usually a very large filters tree), open() rejects with invalid_options.
Result
| What happens | Promise | Callbacks, in order |
|---|---|---|
| The person inserts assets | resolves with the assets | onSelect(assets), then onClose() once the window has closed |
| The person cancels | resolves with null |
onCancel(), then onClose() |
| The person closes the window or the modal | resolves with null |
onCancel(), then onClose() |
| The browser blocks the window | rejects with popup_blocked |
onError(error) |
| The picker reports a blocking error | rejects with the error | onError(error), then onClose() when the person closes the window or the modal |
| An insertion fails | stays pending | onError(error); the person can try again in the same picker |
The workspace refuses the publicKey |
stays pending | onError(error); the person can still sign in |
A cancellation is not an error, so a single null check handles it:
const assets = await open({ workspace: 'acme.freedam.io', integration: 'w5r8n2c4t7bd' });
if (!assets) {
return;
}
The picker window closes itself after an insertion or a cancellation; if it is still open a second later, the SDK closes it. In the modal and inline modes, the SDK removes the iframe at once.
Display modes
Popup
The default. The picker opens in a window of about 1100 by 760 pixels, centred on your page. On Safari for iOS, it opens as a tab. The person closes it like any window.
Tab
open({ workspace: 'acme.freedam.io', integration: 'w5r8n2c4t7bd', mode: 'tab' });
The picker opens in a new tab of the browser, at the full width of the window. The SDK opens the tab with window.open() and no window features, without noopener, so the picker keeps window.opener (see Cross-Origin-Opener-Policy under Host page requirements). It works as the popup does: it sends the selection to your page, then closes, and the browser shows your page's tab again. When the person closes the tab without inserting, the promise resolves with null. Another call to open() while the tab is open brings it to the front; some browsers ignore this for a tab, so tell the person where the picker is. Since version 1.2.
Modal
open({ workspace: 'acme.freedam.io', integration: 'w5r8n2c4t7bd', mode: 'modal' });
The picker opens in an iframe over your page, on a dimmed background, up to 1100 by 760 pixels. Below 640 pixels wide it fills the screen, under a thin bar that holds the close button. The person closes it with the close button, or with the Escape key while the focus is on the close button rather than in the picker. The Tab key keeps the focus inside the modal, your page does not scroll while it is open, and the focus returns to the element that had it.
Inline
open({
workspace: 'acme.freedam.io',
integration: 'w5r8n2c4t7bd',
mode: 'inline',
container: document.getElementById('freedam-picker'),
});
The picker opens in an iframe that fills container, at least 480 pixels high: give the element the size the picker should take. The iframe is removed after an insertion or a cancellation; call open() again to show the picker again. Removing container from the page counts as a cancellation.
When the picker cannot run in the page
In the modal and inline modes, the picker has a session of its own inside the iframe, so the person signs in once for your site (see Sign-in). The SDK switches to a window, with the same options, when:
- the browser refuses the cookie of that session, as some browsers do for any site other than the one in the address bar;
- the person chooses Open in a window on the picker's sign-in screen;
- the picker page sends nothing within 15 seconds, for example because the integration does not list your page's origin and the browser refuses to show it.
The iframe then gives way to a short text and an Open Freedam button: browsers only open a window in response to a click. When the person's click inside the iframe can open the window directly, the SDK opens it without the button. The promise and the callbacks continue with the window: your page handles one outcome whatever the mode.
Web component
<freedam-picker> opens the picker when the person clicks inside it, with the element's attributes as options. It has no shadow DOM, no style and no text: put your own button inside.
<freedam-picker workspace="acme.freedam.io" integration="w5r8n2c4t7bd" select="multiple" max="5">
<button type="button">Choose from Freedam</button>
</freedam-picker>
<script>
document.querySelector('freedam-picker').addEventListener('freedam-select', (event) => {
insertAssets(event.detail.assets);
});
</script>
The script-tag build defines the element. With npm, import '@freedam/picker/element' defines it. A page that loads both defines it once.
Attributes
| Attribute | Option |
|---|---|
workspace, integration, select, max, q, locale, theme, mode |
The options of the same names. |
public-key |
publicKey (version 1.1). |
sort-by, sort-direction |
sort.by and sort.direction. |
filters |
filters, as JSON. Invalid JSON raises invalid_options. |
preselected |
preselected, as comma-separated GAIDs. |
types |
types, as comma-separated types, for example image,video (version 1.2). An empty attribute offers every type of the integration. |
container |
With mode="inline": the id of the element that receives the picker (version 1.1). |
The element reads its attributes when the picker opens: a change affects the next opening. filters, preselected and container are also properties, to pass an object, an array or an element instead of text; a property takes precedence over its attribute.
element.open() opens the picker with the element's attributes and returns the same promise as open(). Call it from a click handler.
Events
The element dispatches these events, which bubble and cross shadow roots:
| Event | detail |
Same as |
|---|---|---|
freedam-ready |
{} |
onReady |
freedam-select |
{ assets } |
onSelect |
freedam-cancel |
{} |
onCancel |
freedam-error |
{ error }, a FreedamPickerError |
onError |
freedam-close |
{} |
onClose |
Errors
Every error is a FreedamPickerError with a code and an English message meant for the developer console. The picker window shows its own message to the person, in their language.
try {
const assets = await open(options);
} catch (error) {
if (error instanceof FreedamPickerError && error.code === 'origin_denied') {
// The integration does not list this page's origin.
}
}
| Code | Raised by | Meaning |
|---|---|---|
origin_denied |
picker | The integration does not exist, or its list does not include your page's origin. One code covers both cases. |
feature_unavailable |
picker | The workspace's plan does not include the Freedam Picker. |
integration_suspended |
picker | An administrator suspended the integration. |
integration_unavailable |
picker | The integration's profile or publication channel is missing or disabled. |
insert_failed |
picker | The insertion failed. The picker stays open and the person can try again; the promise waits for the outcome. |
guest_access_unavailable |
picker | The publicKey is not accepted: the access without an account is off, the key was regenerated, or the workspace's Public Portal is off. The picker still offers to sign in; the promise waits for the outcome. |
popup_blocked |
SDK | The browser blocked the window: call open() from a click handler, with no await before it. In the modal and inline modes, the browser blocked the window opened by the Open Freedam button. |
invalid_options |
SDK | An option is missing or invalid, the opening URL is too long, or the page has no HTTP(S) origin (file://). |
The picker reports origin_denied, feature_unavailable, integration_suspended and integration_unavailable before asking anyone to sign in.
The picker in an iframe also sends auth_required when it cannot sign in there. The SDK handles it by switching to a window (see Display modes); it never reaches onError or the promise.
Sign-in
The picker acts for the person using it: it shows only the assets they can see, and the insertion is recorded under their name. Someone already signed in to the workspace in the same browser sees the picker at once.
Someone who is not signed in sees a Sign in to Freedam button in the picker window. The button opens a second window on the workspace, where every sign-in method of the workspace works: password, passkey, two-factor authentication, single sign-on with Google or Microsoft. Once the person is signed in, the second window closes and the picker reloads with the same options.
The picker window itself never leaves the workspace host. Sign-in pages of identity providers can cut the link between a window and the page that opened it; keeping them in the second window keeps the link between the picker and your page, so the selection always reaches you. Your page has nothing to handle: open() resolves the same way.
In the tab mode, and on Safari for iOS, the picker and the sign-in window open as tabs and work the same way.
In the modal and inline modes
The iframe cannot use the person's Freedam session: browsers keep the cookies of a site shown inside another site apart. The picker's sign-in screen in the iframe has a Sign in to Freedam button that opens a small window on the workspace; the person signs in there as above (or is recognised at once if already signed in), the window closes, and the picker in the iframe is signed in for as long as the person keeps using it on your site. Its session is kept apart for each site that shows the picker.
When the browser refuses that session, or when the person prefers it, the picker continues in a window (see When the picker cannot run in the page).
Host page requirements
- User gesture. Call
open()in the handler of a click or key press, before anyawait. - Cross-Origin-Opener-Policy. If your page sends this header, use
same-origin-allow-popups(orunsafe-none). Withsame-origin, the browser cuts the link to the picker window and the selection never arrives: the picker then tells the person that the page that opened it is no longer reachable. - Content-Security-Policy. Allow the workspace host in
script-srcwhen you load the SDK from it, and inframe-srcfor themodalandinlinemodes. To display what the picker returns, allow the workspace host inimg-src(images, posters, document previews) and inframe-src(video player, document viewer). The SDK sets its styles through the CSSOM, sostyle-srcneeds nounsafe-inline. - Framing. In the
modalandinlinemodes, the workspace lets only the integration's origins show the picker in an iframe (frame-ancestors). Do not add asandboxattribute around the SDK's iframe: the sign-in screen opens a window. - Origin. Your page must be served over HTTPS (or
http://localhostduring development, if the integration lists it) from an origin the integration lists.
Payload
open() resolves with an array of assets in the order the person chose them, and onSelect receives the same array. Each asset is an object whose type is image, video or document. Keys are snake_case, like the Freedam API.
The JSON Schema of an asset (JSON Schema 2020-12) is served at https://{workspace}/sdk/picker/schema/v1/payload.schema.json and ships in the npm package as @freedam/picker/schema.json. The package's TypeScript types are PickerAsset, PickerImage, PickerVideo and PickerDocument.
Fields of every asset
| Field | Type | Description |
|---|---|---|
gaid |
string | Global asset ID; use it with GET /api/v1/assets/{gaid}. |
version |
integer | Asset version at insertion. The URLs always serve the current version. |
type |
string | image, video or document. |
mime_type |
string | MIME type of the original file. |
file_size |
integer | Size of the original file, in bytes. |
width, height |
integer or null | Dimensions of the original file, in pixels; null for a document without dimensions. |
url |
string | Main URL for what the person chose (see each type). |
url_template |
string | URL of the asset's image with {options} where the image options go (see Image URLs). |
widths |
integer[] | Widths, in pixels, that url_template serves with w=, smallest first: the channel's widths up to the width of the original. When the original is narrower than every channel width, the smallest one; when its width is unknown, all of them. Since version 1.2. |
formats |
array | Named formats of the integration's publication channel: { name, ratio, url }, where ratio is written 16:9 and url is the asset's image in that format at the channel's largest width. |
focus_point |
object or null | { x, y } in percent from the top-left corner, or null. |
thumbnail_url |
string | The asset's image at the smallest width the channel serves from 400 pixels up, or at the channel's largest width when it is narrower. |
metadata |
object | Metadata fields the integration exposes (see Metadata). |
usage_id |
string | Identifier (a UUID) of the usage recorded for this insertion. |
usage_token |
string | Token that authorizes the usage operations of this insertion, and nothing else (see Usages). It starts with fdu_. |
choice |
object | What the person set in the picker (see each type). |
The asset's image is the image itself, a video's poster or a document's first page. url_template, widths, formats, focus_point and thumbnail_url refer to it for all three types, so the same code displays a thumbnail or a cover for any asset.
Store usage_id and usage_token with your content. Freedam lists the insertion on the asset's page, so people in the workspace know where the asset is used before they change or delete it.
Images
url is the image in the format and file format the person chose, at the channel's largest width. Use url_template for other sizes.
Field of choice |
Values |
|---|---|
format |
Name of the chosen format, or null for the original ratio. |
file_format |
avif, webp, jpeg, or null: the browser receives the best format it accepts. |
{
"gaid": "IM-20260309-UDXZH8PP6VPW",
"version": 3,
"type": "image",
"mime_type": "image/jpeg",
"file_size": 4821331,
"width": 6000,
"height": 4000,
"url": "https://acme.freedam.io/i/w=2048,fmt=hero/IM-20260309-UDXZH8PP6VPW/k3j9x2m1p0qa/5f1c2e",
"url_template": "https://acme.freedam.io/i/{options}/IM-20260309-UDXZH8PP6VPW/k3j9x2m1p0qa/5f1c2e",
"widths": [128, 256, 512, 1024, 2048],
"formats": [
{ "name": "hero", "ratio": "16:9", "url": "https://acme.freedam.io/i/w=2048,fmt=hero/IM-20260309-UDXZH8PP6VPW/k3j9x2m1p0qa/5f1c2e" },
{ "name": "card", "ratio": "1:1", "url": "https://acme.freedam.io/i/w=2048,fmt=card/IM-20260309-UDXZH8PP6VPW/k3j9x2m1p0qa/5f1c2e" }
],
"focus_point": { "x": 45.2, "y": 38.7 },
"thumbnail_url": "https://acme.freedam.io/i/w=512/IM-20260309-UDXZH8PP6VPW/k3j9x2m1p0qa/5f1c2e",
"metadata": {
"title": "Head office, Lyon",
"alt": "Glass front of an office building at sunset",
"credit": "Studio Martin",
"copyright": null,
"custom": { "product_ref": "SKU-1042" }
},
"usage_id": "9d4e1f3a-6b2c-4f8e-a1d7-3c5b9e2f0a64",
"usage_token": "fdu_Xq7T2mLp9RkV4cWn8YbH3dJs6FgZ1aNe5uQo0iKyBtCvMrEw",
"choice": { "format": "hero", "file_format": null }
}
Videos
Videos stream over HLS, through the channel's player or your own.
| Field | Type | Description |
|---|---|---|
url |
string | Equal to player_url. |
player_url |
string | Freedam player, with the settings of choice. |
embed_html |
string | Responsive <iframe> of player_url, full width at the video's ratio. |
stream_url |
string | HLS manifest, for your own player. |
poster_url |
string | Poster image, 1024 pixels wide, or at the channel's largest width when it is narrower. It is the asset's image, so url_template, widths and formats give it at other sizes. |
duration |
integer or null | Duration, in whole seconds; null when unknown. |
subtitles |
array | Subtitle tracks: { language, label, url, is_default }. |
choice |
object | autoplay, muted, loop, controls (booleans), start (seconds), subtitle_language: null shows the video's default subtitle track, "off" shows none, a language code shows that track. autoplay: true always comes with muted: true, as browsers require. |
If your application does not insert third-party HTML, build your own <iframe> from player_url instead of using embed_html.
Documents
| Field | Type | Description |
|---|---|---|
url |
string | download_url or pdf_url, as choice.file says. |
download_url |
string | Original file. |
pdf_url |
string or null | PDF version; the same as download_url for a PDF; null when no PDF version exists. |
preview_url |
string or null | First page, as an image; null when the document has no preview. |
viewer_url |
string or null | Page-by-page viewer; null when no PDF version exists. |
embed_html |
string or null | Responsive <iframe> of viewer_url; null when no PDF version exists. |
page_count |
integer or null | Number of pages. |
extension |
string | Extension of the original file. |
choice |
object | form: link, thumbnail or viewer; file: original or pdf. |
choice.form tells your application what to insert: a link to url, the preview_url image linked to url, or embed_html. The picker offers viewer and pdf only for documents with a PDF version.
Metadata
metadata contains the fields the integration exposes, and only those:
- built-in fields under fixed keys:
title,description,alt,credit,copyright(strings),keywords(an array of strings); - the workspace's own metadata fields under
custom, by field key, so a workspace field namedtitlenever collides with the built-in one. A value is what the Freedam API returns for the field: a string, a number, a boolean, a list for several values, or an object of language code to text for a multilingual field. Dates areYYYY-MM-DD.
An exposed field is always present. Its value is null when it is empty, or when the person who inserted the asset is not allowed to see it.
Image URLs
url_template contains the literal text {options}. Replace it with comma-separated options to get any size of the asset's image. The URL keeps its signature, so you need no token and no call to Freedam.
| Option | Example | Effect |
|---|---|---|
w |
w=800 |
Width in pixels. |
h |
w=800,h=600 |
Height, with w only: the image is cropped to the ratio w / h, around the focus point. |
fmt |
fmt=hero |
One of the integration's named formats: its ratio, cropped around the focus point. Not combined with h. |
fit |
fit=cover |
Optional: cropping is the only fit, and h or fmt always crop. |
f |
f=webp |
File format: avif, webp, jpeg, jpg, png. Without it, the browser receives the best format it accepts. |
Width and height are free, within these rules:
- Width. The server rounds the width up to the next width the publication channel serves. Ask for the width your layout needs, doubled for high-density screens (there is no
dproption).widthslists the widths the channel serves for the asset: buildsrcsetfrom it, since a width above the channel's largest one returns400. - Ratio. With
h, the ratiow / hmust be between 1:4 and 4:1. The server rounds it to the hundredth, then computes the height from the rounded width:w=1000,h=562andfmt=hero(16:9) give the same image. - Cropping. With
horfmt, the image is cropped around the asset's focus point, or around its centre when it has none. - Errors. A width above the channel's largest width,
hwithoutw, a ratio outside 1:4 to 4:1, an unknown format name or an unknown option returns400.
Responsive image from the template:
const src = (options) => asset.url_template.replace('{options}', options);
const img = document.createElement('img');
img.src = src('w=800,h=450');
img.srcset = asset.widths.map((width) => `${src(`w=${width},h=${Math.round(width * 9 / 16)}`)} ${width}w`).join(', ');
img.sizes = '(min-width: 1024px) 50vw, 100vw';
img.alt = asset.metadata.alt ?? '';
Focus point
focus_point gives the subject's position in percent from the top-left corner. When your layout crops the image itself with CSS, keep the subject visible with object-position:
.cover {
width: 100%;
aspect-ratio: 3 / 1;
object-fit: cover;
object-position: var(--focus-x, 50%) var(--focus-y, 50%);
}
if (asset.focus_point) {
img.style.setProperty('--focus-x', `${asset.focus_point.x}%`);
img.style.setProperty('--focus-y', `${asset.focus_point.y}%`);
}
Keeping a copy
The URLs always serve the asset's current version. A host that stores a copy of the image instead, such as an e-commerce platform that keeps product media, reads the version it copied from the X-Freedam-Asset-Version header of the response, and later compares it with current_version from Read a usage.
While the width you ask for is still being rendered, the server answers at once with a smaller image and the headers X-Image-Fallback: 1 and X-Image-Fallback-Reason, with a cache of 5 seconds. That response has no X-Freedam-Asset-Version: do not keep it, and ask again a little later.
The image options and caching of channel URLs are also described in Image Transformations.
Usages
Each insertion records a usage: the asset's page in Freedam lists it, with the integration, so people know where the asset is used. Calls of the Freedam API read, describe or remove it. They take the insertion's usage_token as their only credential, with no API token:
curl -X POST https://acme.freedam.io/api/v1/usages \
-H "Authorization: Bearer fdu_…" \
-H "Content-Type: application/json" \
-d '{"page_url": "https://www.example.com/products/taper-bowl", "reference": "SKU-1042", "label": "Taper bowl product page"}'
| Field | Description |
|---|---|
page_url |
The page that shows the asset, http or https, 2,048 characters at most. |
reference |
A product reference or content identifier of your system, 255 characters at most. |
label |
A readable label, such as the page title, 255 characters at most. |
Send at least one field. Each call replaces the previous description: a field left out becomes null. The answer is the usage, with id, asset_id (the GAID), integration_id, the three fields, inserted_at and updated_at.
When your content no longer shows the asset, remove the usage:
curl -X DELETE https://acme.freedam.io/api/v1/usages/9d4e1f3a-6b2c-4f8e-a1d7-3c5b9e2f0a64 \
-H "Authorization: Bearer fdu_…"
The answer is 204. The asset stays published: only its publication channel decides what is served. Treat 401 or 404 on a removal as a usage already removed.
401: the token is missing or unknown, its usage was removed, or its integration was deleted.403: the workspace no longer has the Freedam Picker.404: theusage_idis not the usage of the token.429: more than 30 requests a minute for one token, or 300 for one address.
Read a usage
GET /api/v1/usages/{usage_id} tells you what Freedam knows now about the asset you inserted, without a request to its URLs: whether its publication channel can still serve it, when its rights end, its current version and its metadata.
curl https://acme.freedam.io/api/v1/usages/9d4e1f3a-6b2c-4f8e-a1d7-3c5b9e2f0a64 \
-H "Authorization: Bearer fdu_…"
{
"usage_id": "9d4e1f3a-6b2c-4f8e-a1d7-3c5b9e2f0a64",
"removed_at": null,
"integration_key": "k3j9x2m1p0qa",
"channel_key": "w7c1r5t8n2bd",
"asset": {
"gaid": "IM-20260309-UDXZH8PP6VPW",
"inserted_version": 3,
"current_version": 4,
"availability": "available",
"rights_valid_until": "2027-03-31T00:00:00+00:00",
"metadata": {
"title": "Head office, Lyon",
"alt": "Glass front of an office building at sunset",
"credit": "Studio Martin"
}
}
}
| Field | Description |
|---|---|
usage_id |
The usage. |
removed_at |
When the usage was removed, or null. A removed usage is read until it is deleted, twelve months after its removal; declare it again if your content shows the asset again. |
integration_key |
The integration the asset was inserted with. |
channel_key |
The publication channel whose URLs you received, or null when it was deleted. |
asset.gaid |
The asset. |
asset.inserted_version |
The asset's version at insertion; null for usages recorded before Freedam kept it. A copy of a usage keeps the version of the usage it was copied from. |
asset.current_version |
The asset's version now, the one its URLs serve. |
asset.availability |
Whether the channel can serve the asset; see below. |
asset.rights_valid_until |
End of the asset's rights, ISO 8601, or null when they have no end date. |
asset.metadata |
The fields the integration exposes, as in the payload. A field the workspace marks as sensitive is null. |
availability takes one of these values. Only available means the URLs serve the asset; when several reasons apply, you receive the first one of the table.
| Value | Meaning |
|---|---|
available |
The channel serves the asset. |
channel_stopped |
The channel was stopped or deleted. |
deleted |
The asset is in the workspace's trash. |
archived |
The asset is archived. |
excluded |
Someone in the workspace unpublished the asset from the channel. |
rights_expired |
The asset's rights have expired. |
not_published |
The channel no longer publishes the asset. |
not_eligible |
The asset's rights or type no longer allow this channel. |
rights_expired and not_eligible mean the rights no longer allow showing the asset: remove your copies. The other values say the asset is no longer served, without a conclusion about its rights. When rights_valid_until is close, the asset stays available until that date.
401: the token is missing or unknown, belongs to another usage, its usage was removed more than twelve months ago, its integration was deleted, or the asset was deleted permanently.403: the workspace no longer has the Freedam Picker.429: more than 30 requests a minute for one token, or 300 for one address, counted with the other usage calls.
Keep the usage token in your editor or on your server, never in the published page: anyone holding it can change or remove the usage.
Protocol
The SDK implements this protocol; the description below is for applications that cannot load it.
Opening URL
Open https://{workspace}/picker/{integration} in a popup or a tab with window.open(), without noopener, or in an iframe without sandbox, with these query parameters:
| Parameter | Value |
|---|---|
protocol |
1 |
origin |
Your page's origin, window.location.origin. The server checks it against the integration's list and against the Referer header. |
channel |
A random value, 22 base64url characters (128 bits), new for each opening. |
mode |
popup or tab for a window, modal or inline for an iframe. The server starts the iframe's own session from it. |
select |
single or multiple |
max |
1 to 50, with select=multiple |
q, filters, sort_by, sort_direction, preselected, types, locale, theme |
As the options of the same names; filters is JSON, preselected and types are comma-separated. |
public_key |
As the publicKey option. |
Messages
The picker posts every message to your page's origin only, never to *. Each message has the fields source: "freedam-picker", protocol: 1 and the channel of the opening, plus:
type |
Fields | When |
|---|---|---|
ready |
integration |
The picker is displayed. Sent on each page load, including after sign-in. |
select |
assets |
The insertion succeeded. The picker window closes next. |
cancel |
none | The person pressed Cancel. The picker window closes next. |
error |
code |
An error the picker also shows to the person; the window stays open. |
sign_in |
none | Iframe only: the picker shows its sign-in screen; ready follows once the person has signed in. |
resize |
height |
Iframe only: the height in CSS pixels the picker's content needs. The SDK applies it to the inline iframe, never below 480 pixels. |
Accept a message only when event.origin is https://{workspace}, event.source is the window you opened (or the iframe's contentWindow), and source, protocol and channel match. Ignore every other message, and any type you do not know: new message types may be added.
In a window, your page sends no message to the picker. With an iframe, post { source: "freedam-picker", protocol: 1, channel, type: "close" } to its contentWindow, with https://{workspace} as the target origin, just before you remove it; the picker does not answer.
To detect that the person closed the window, check the window's closed property periodically (the SDK checks every 500 ms). An iframe that sends no message within a reasonable time (the SDK waits 15 seconds), or that sends error with auth_required, cannot show the picker: open the same URL with mode=popup in a window, from a click.
Versions
| Version | Adds | integrity of https://{workspace}/sdk/picker/{version}/picker.js |
|---|---|---|
| 1.2.0 | The tab mode, the types option and attribute, the widths payload field. |
sha384-NngTI5clKCwu+PbhM6M3EDuOWhcywXV3y8qOJ1DrH7cAs8HfCoJgSdeMCuO8rgo/ |
| 1.1.0 | The modal and inline modes, the container and publicKey options, the switch to a window when the picker cannot run in the page, the sign_in and resize messages. guest_access_unavailable keeps the promise pending. |
sha384-+YgSwNjdAmJuXKbGTClOVNARH6rbYHhliSwyWOB+ZhwjX59LSMuSwnQR7smcmt3I |
| 1.0.0 | open() in a window, its options, callbacks and error codes, the <freedam-picker> element, the payload and protocol version 1. |
sha384-SYiykeNpX3DaSiYEV98NDoh7sCNYAAr4FhiOMzT+ySGecEkyxIHqFhTva4C5RAH4 |
https://{workspace}/sdk/picker/v1/picker.js serves the latest 1.x version, today 1.2.0, and VERSION gives the version a page loaded. To pin a version, load its own URL with its integrity and crossorigin="anonymous" (see Script tag); with npm, pin the package version.
The SDK follows semantic versioning. A major version changes the JavaScript API or the protocol incompatibly, or removes a payload field; a minor version adds an option, an event or a payload field; a patch fixes the rest. Read payloads tolerantly: new fields may appear in a minor version.
The protocol has its own version number, sent as protocol. When a new protocol version appears, Freedam keeps accepting the previous one for at least six months. Deprecations are announced six months ahead on this page, in the package's changelog and on the What's new page.