Skip to content
Developers
Open app

Create a view

POST
/v1/library-views
const url = 'https://api.focal.inc/v1/library-views';
const options = {
method: 'POST',
headers: {
'Idempotency-Key': 'looker-sync-2027-03-04-0001',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: '{"space_id":"6916bc3d-e1ab-425b-ac25-eefada01a9a2","name":"Approved for Meta","view_mode":"gallery","filter":{"type":"video","status":"Approved","created_after":"2027-03-01"},"sort":{"field":"uploadedAt","order":"asc"},"group_by":"status","visible_fields":["name","status","type","uploadedAt","uploadedById"],"order":1}'
};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}

Save a search over a space’s media library as a view. filter takes the same shape as POST /v1/media/search, so a search you have already run can be saved as-is.

Everything but space_id is optional: a view with no filter shows the whole library. Omit order to append the view to the end of the space’s list. Library views are shared: every member of the space sees, and can edit, every view in it. There is no private view.

Required scope: media:write. Rate-limit cost: 10. An Idempotency-Key header is required.

Idempotency-Key
required
string
<= 255 characters

Example

looker-sync-2027-03-04-0001

Client-generated key (≤255 chars) for safe retries. A retry with the same key replays the original response instead of re-applying the write; reusing a key with a different body → 400, an in-flight key → 409. Required on POST; optional on PATCH/DELETE. A UUID v4 is recommended — generate one per logical write and reuse it only for retries of that write.

Media typeapplication/json
object
space_id
required

The space whose library this view belongs to.

string format: uuid

Example

6916bc3d-e1ab-425b-ac25-eefada01a9a2
name

Display name of the view. 1–255 characters.

string
nullable >= 1 characters <= 255 characters

Example

Approved for Meta
view_mode

Defaults to gallery.

string
Allowed values: gallery grid
filter

The saved search this view runs, in the same vocabulary as POST /v1/media/search — every entry is ANDed. null means the view shows the whole library unfiltered.

include_subfolders, sort and order are absent on purpose: a view doesn’t store folder recursion (the app settles that when the view runs), and result ordering lives in the view’s own sort.

object
query

Free-text query, ranked by relevance (≤200 chars). Matches names, descriptions, folder names, tags and attribute values; in spaces with Focal AI enabled it also matches text detected in the asset (OCR), speech transcripts, and the visual content itself. A media id here matches that asset exactly.

string
<= 200 characters

Example

trail runner at sunrise
type

Restrict to one file type.

string
Allowed values: video image playable file
folder_id

Restrict to a folder in the searched space. Subfolders are included unless include_subfolders is false. Unknown folders are rejected rather than ignored.

string format: uuid

Example

80539d38-3a8e-48f8-929b-8c0e9a22ab43
status

Restrict to media with this status — an id or name from the space’s catalog (the Space resource’s media_statuses), the same id-or-name contract as a status write.

string
>= 1 characters <= 255 characters

Example

Approved
uploaded_by

Restrict to media uploaded by this user (a GET /v1/users id).

string format: uuid

Example

cfebb6ef-f092-4591-a6e3-e6667ab5a252
attributes

Filter on media attribute (custom field) values. Media must satisfy every entry; within an entry, values is any-of. number and date attributes aren’t searchable yet and are rejected.

Array<object>
object
attribute_id
required

The media attribute (custom field) to filter on — an id from GET /v1/media-attributes.

string format: uuid

Example

846196ad-9fa2-40a8-8938-655a962220e1
values
required

Values to match, interpreted per the attribute’s type: option ids or names for a tags field, user ids for a user field, "true" / "false" for a checkbox field (exactly one), and the stored text for text / link. Several values match any of them. An option value matching none of the attribute’s options — and a user value that isn’t a user id — is rejected rather than ignored, so a filter never silently widens the result set.

Array<string>

Example

[
"Spring Launch"
]
match

any (default) keeps media matching at least one value; none excludes media matching any of them.

string
Allowed values: any none
created_after
Any of:
string format: date
created_before
Any of:
string format: date
last_activity_after
Any of:
string format: date
last_activity_before
Any of:
string format: date
metric_filters

Narrow to media whose ad-platform metrics satisfy every one of these (ANDed, like the other filters). Compares against lifetime totals — { metric: "spend", operator: "greater_than", value: 1000 } means “has spent over 1000 in total”, not “spent over 1000 in some window”. Media with no data for the metric never match.

Array<object>
object
metric
required

Which metric to compare — one of the keys that appear in a media’s metrics.insights.

string
Allowed values: spend impressions clicks reach installs watch_min_3_seconds watch_hook_rate_min_3_seconds
operator
required

How to compare — greater_than, less_than, or equals.

string
Allowed values: greater_than less_than equals
value
required

The bound to compare the metric’s lifetime total against.

number

Example

1000

Example

{
"type": "video",
"status": "Approved",
"created_after": "2027-03-01"
}
sort
object
field
required

The column to sort by — a built-in library column key (name, status, type, size, uploadedById, uploadedAt, …) or a media attribute id from GET /v1/media-attributes.

string
>= 1 characters <= 255 characters

Example

uploadedAt
order
required
string
Allowed values: asc desc
group_by

Column to group by. Same vocabulary as sort.field.

string
nullable >= 1 characters <= 255 characters

Example

status
visible_fields

Columns to show, in display order. Built-in library column keys or media attribute ids from GET /v1/media-attributes.

Array<string>
nullable

Example

[
"name",
"status",
"type",
"uploadedAt",
"uploadedById"
]
order

Position in the space’s view list. Omit on create to append to the end.

integer
<= 1000000

Example

1

Created

Media typeapplication/json
object
data
required
object
id
required
string format: uuid
name
required

Display name. null on a view the app created without one — it renders as “Untitled view” until a name is set.

string
nullable
space_id
required
string format: uuid
organization_id
required
string format: uuid
view_mode
required

How the view renders its media: gallery (cards) or grid (table).

string
Allowed values: gallery grid
filter
required

The saved search this view runs, in the same vocabulary as POST /v1/media/search — every entry is ANDed. null means the view shows the whole library unfiltered.

include_subfolders, sort and order are absent on purpose: a view doesn’t store folder recursion (the app settles that when the view runs), and result ordering lives in the view’s own sort.

object
query

Free-text query, ranked by relevance (≤200 chars). Matches names, descriptions, folder names, tags and attribute values; in spaces with Focal AI enabled it also matches text detected in the asset (OCR), speech transcripts, and the visual content itself. A media id here matches that asset exactly.

string
<= 200 characters
type

Restrict to one file type.

string
Allowed values: video image playable file
folder_id

Restrict to a folder in the searched space. Subfolders are included unless include_subfolders is false. Unknown folders are rejected rather than ignored.

string format: uuid
status

Restrict to media with this status — an id or name from the space’s catalog (the Space resource’s media_statuses), the same id-or-name contract as a status write.

string
>= 1 characters <= 255 characters
uploaded_by

Restrict to media uploaded by this user (a GET /v1/users id).

string format: uuid
attributes

Filter on media attribute (custom field) values. Media must satisfy every entry; within an entry, values is any-of. number and date attributes aren’t searchable yet and are rejected.

Array<object>
object
attribute_id
required

The media attribute (custom field) to filter on — an id from GET /v1/media-attributes.

string format: uuid
values
required

Values to match, interpreted per the attribute’s type: option ids or names for a tags field, user ids for a user field, "true" / "false" for a checkbox field (exactly one), and the stored text for text / link. Several values match any of them. An option value matching none of the attribute’s options — and a user value that isn’t a user id — is rejected rather than ignored, so a filter never silently widens the result set.

Array<string>
match

any (default) keeps media matching at least one value; none excludes media matching any of them.

string
Allowed values: any none
created_after
Any of:
string format: date
created_before
Any of:
string format: date
last_activity_after
Any of:
string format: date
last_activity_before
Any of:
string format: date
metric_filters

Narrow to media whose ad-platform metrics satisfy every one of these (ANDed, like the other filters). Compares against lifetime totals — { metric: "spend", operator: "greater_than", value: 1000 } means “has spent over 1000 in total”, not “spent over 1000 in some window”. Media with no data for the metric never match.

Array<object>
object
metric
required

Which metric to compare — one of the keys that appear in a media’s metrics.insights.

string
Allowed values: spend impressions clicks reach installs watch_min_3_seconds watch_hook_rate_min_3_seconds
operator
required

How to compare — greater_than, less_than, or equals.

string
Allowed values: greater_than less_than equals
value
required

The bound to compare the metric’s lifetime total against.

number
filter_complete
required

Whether filter describes the view’s saved search in full. false means the stored search uses a condition this API can’t express (the app’s filter builder is wider than the public vocabulary), so filter under-describes the view — sending it back in a PATCH would drop the rest. Omit filter from a PATCH to leave the saved search untouched.

boolean
sort
required

Result ordering; null means the library default.

object
field
required

The column to sort by — a built-in library column key (name, status, type, size, uploadedById, uploadedAt, …) or a media attribute id from GET /v1/media-attributes.

string
>= 1 characters <= 255 characters
order
required
string
Allowed values: asc desc
group_by
required

Column the view groups its media by, or null for no grouping. Same vocabulary as sort.field.

string
nullable
visible_fields
required

Columns shown, in display order. Same vocabulary as sort.field. null means the view never pinned a set and follows the library default.

Array<string>
nullable
order
required

Position in the space’s view list, ascending. Not guaranteed unique or gap-free.

integer
created_at
required
string format: date-time
updated_at
required
string format: date-time
html_url
required

Browser URL that opens this view in the Focal web app. Hand this to users as a clickable link.

string

Example

{
"data": {
"id": "ac5a4a25-8554-4c96-8ade-cb57a1c43b93",
"name": "Approved for Meta",
"space_id": "6916bc3d-e1ab-425b-ac25-eefada01a9a2",
"organization_id": "b46d9033-24b2-4441-87ad-2151088fde01",
"view_mode": "gallery",
"filter": {
"type": "video",
"status": "Approved",
"created_after": "2027-03-01"
},
"filter_complete": true,
"sort": {
"field": "uploadedAt",
"order": "desc"
},
"group_by": "status",
"visible_fields": [
"name",
"status",
"type",
"uploadedAt",
"uploadedById"
],
"order": 1,
"created_at": "2027-03-04T10:15:00Z",
"updated_at": "2027-03-06T14:42:00Z",
"html_url": "https://app.focal.inc/spaces/6916bc3d-e1ab-425b-ac25-eefada01a9a2/library/views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93"
}
}

The request was malformed (invalid cursor, unknown type, validation failure, etc.). error.code is bad_request.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "bad_request",
"message": "Invalid request body",
"details": [
{
"field": "space_id",
"issue": "Invalid UUID"
}
]
}
}

Missing, invalid, or disabled token. error.code is unauthenticated.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "unauthenticated",
"message": "Bearer token is missing or invalid"
}
}

The token doesn’t have the scope this endpoint requires. error.code is forbidden.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "forbidden",
"message": "Token doesn't have the required scope"
}
}

Resource doesn’t exist or is outside the token’s space allow-list. The two cases share a response shape so a restricted token can’t probe for the existence of out-of-scope spaces. error.code is not_found.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "not_found",
"message": "Not found"
}
}

The request conflicts with the current state. Most commonly a concurrent request carrying the same Idempotency-Key is still in flight. error.code is conflict.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "conflict",
"message": "A request with this Idempotency-Key is already being processed"
}
}

Rate limit exceeded. error.code is rate_limited. Wait the number of seconds in the Retry-After header before retrying. Quotas are per-token; see the “Rate limiting” section at the top of this document.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "rate_limited",
"message": "Rate limit of 1000 units per 60s exceeded (this request cost 5)"
}
}
Retry-After
integer
Example
12

Seconds to wait before retrying.