Update a view
CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "PATCH");curl_easy_setopt(hnd, CURLOPT_URL, "https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93");
struct curl_slist *headers = NULL;headers = curl_slist_append(headers, "Idempotency-Key: looker-sync-2027-03-04-0001");headers = curl_slist_append(headers, "Authorization: Bearer <token>");headers = curl_slist_append(headers, "Content-Type: application/json");curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, "{ \"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 }");
CURLcode ret = curl_easy_perform(hnd);using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Patch, RequestUri = new Uri("https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93"), Headers = { { "Idempotency-Key", "looker-sync-2027-03-04-0001" }, { "Authorization", "Bearer <token>" }, }, Content = new StringContent("{ \"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 }") { Headers = { ContentType = new MediaTypeHeaderValue("application/json") } }};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93"
payload := strings.NewReader("{ \"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 }")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Idempotency-Key", "looker-sync-2027-03-04-0001") req.Header.Add("Authorization", "Bearer <token>") req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93")) .header("Idempotency-Key", "looker-sync-2027-03-04-0001") .header("Authorization", "Bearer <token>") .header("Content-Type", "application/json") .method("PATCH", HttpRequest.BodyPublishers.ofString("{ \"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 }")) .build();HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");RequestBody body = RequestBody.create(mediaType, "{ \"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 }");Request request = new Request.Builder() .url("https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93") .patch(body) .addHeader("Idempotency-Key", "looker-sync-2027-03-04-0001") .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json") .build();
Response response = client.newCall(request).execute();import axios from 'axios';
const options = { method: 'PATCH', url: 'https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93', headers: { 'Idempotency-Key': 'looker-sync-2027-03-04-0001', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, data: { 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 { data } = await axios.request(options); console.log(data);} catch (error) { console.error(error);}const url = 'https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93';const options = { method: 'PATCH', headers: { 'Idempotency-Key': 'looker-sync-2027-03-04-0001', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"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);}val client = OkHttpClient()
val mediaType = MediaType.parse("application/json")val body = RequestBody.create(mediaType, "{ \"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 }")val request = Request.Builder() .url("https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93") .patch(body) .addHeader("Idempotency-Key", "looker-sync-2027-03-04-0001") .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json") .build()
val response = client.newCall(request).execute()use std::str::FromStr;use serde_json::json;use reqwest;
#[tokio::main]pub async fn main() { let url = "https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93";
let payload = json!({ "name": "Approved for Meta", "view_mode": "gallery", "filter": json!({ "type": "video", "status": "Approved", "created_after": "2027-03-01" }), "sort": json!({ "field": "uploadedAt", "order": "asc" }), "group_by": "status", "visible_fields": ("name", "status", "type", "uploadedAt", "uploadedById"), "order": 1 });
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Idempotency-Key", "looker-sync-2027-03-04-0001".parse().unwrap()); headers.insert("Authorization", "Bearer <token>".parse().unwrap()); headers.insert("Content-Type", "application/json".parse().unwrap());
let client = reqwest::Client::new(); let response = client.request(reqwest::Method::from_str("PATCH").unwrap(), url) .headers(headers) .json(&payload) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}curl --request PATCH \ --url https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93 \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: looker-sync-2027-03-04-0001' \ --data '{ "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 }'wget --quiet \ --method PATCH \ --header 'Idempotency-Key: looker-sync-2027-03-04-0001' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --body-data '{ "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 }' \ --output-document \ - https://api.focal.inc/v1/library-views/ac5a4a25-8554-4c96-8ade-cb57a1c43b93Change a saved view. Only the fields you include change; a null clears the field it names.
filter REPLACES the view’s whole saved search. Check the view’s filter_complete first: when it is false the stored search uses a condition this API can’t express, so writing back the filter you read would silently drop it. Omitting filter leaves the saved search untouched, which is what renaming or re-sorting a view should do.
Required scope: media:write. Rate-limit cost: 10. Honors an optional Idempotency-Key.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Example
ac5a4a25-8554-4c96-8ade-cb57a1c43b93Header Parameters
Section titled “Header Parameters”Example
looker-sync-2027-03-04-0001Client-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.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Display name of the view. 1–255 characters.
Example
Approved for MetaThe 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
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.
Example
trail runner at sunriseRestrict to one file type.
Restrict to a folder in the searched space. Subfolders are included unless include_subfolders is false. Unknown folders are rejected rather than ignored.
Example
80539d38-3a8e-48f8-929b-8c0e9a22ab43Restrict 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.
Example
ApprovedRestrict to media uploaded by this user (a GET /v1/users id).
Example
cfebb6ef-f092-4591-a6e3-e6667ab5a252Filter 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.
object
The media attribute (custom field) to filter on — an id from GET /v1/media-attributes.
Example
846196ad-9fa2-40a8-8938-655a962220e1Values 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.
Example
[ "Spring Launch"]any (default) keeps media matching at least one value; none excludes media matching any of them.
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.
object
Which metric to compare — one of the keys that appear in a media’s metrics.insights.
How to compare — greater_than, less_than, or equals.
The bound to compare the metric’s lifetime total against.
Example
1000Example
{ "type": "video", "status": "Approved", "created_after": "2027-03-01"}object
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.
Example
uploadedAtColumn to group by. Same vocabulary as sort.field.
Example
statusColumns to show, in display order. Built-in library column keys or media attribute ids from GET /v1/media-attributes.
Example
[ "name", "status", "type", "uploadedAt", "uploadedById"]Position in the space’s view list. Omit on create to append to the end.
Example
1Responses
Section titled “Responses”OK
object
object
Display name. null on a view the app created without one — it renders as “Untitled view” until a name is set.
How the view renders its media: gallery (cards) or grid (table).
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
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.
Restrict to one file type.
Restrict to a folder in the searched space. Subfolders are included unless include_subfolders is false. Unknown folders are rejected rather than ignored.
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.
Restrict to media uploaded by this user (a GET /v1/users id).
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.
object
The media attribute (custom field) to filter on — an id from GET /v1/media-attributes.
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.
any (default) keeps media matching at least one value; none excludes media matching any of them.
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.
object
Which metric to compare — one of the keys that appear in a media’s metrics.insights.
How to compare — greater_than, less_than, or equals.
The bound to compare the metric’s lifetime total against.
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.
Result ordering; null means the library default.
object
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.
Column the view groups its media by, or null for no grouping. Same vocabulary as sort.field.
Columns shown, in display order. Same vocabulary as sort.field. null means the view never pinned a set and follows the library default.
Position in the space’s view list, ascending. Not guaranteed unique or gap-free.
Browser URL that opens this view in the Focal web app. Hand this to users as a clickable link.
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.
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
object
object
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.
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
object
object
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.
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
object
object
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.
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
object
object
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.
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
object
object
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.
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
object
object
Example
{ "error": { "code": "rate_limited", "message": "Rate limit of 1000 units per 60s exceeded (this request cost 5)" }}Headers
Section titled “Headers”Example
12Seconds to wait before retrying.