Get an upload
CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");curl_easy_setopt(hnd, CURLOPT_URL, "https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6");
struct curl_slist *headers = NULL;headers = curl_slist_append(headers, "Authorization: Bearer <token>");curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
CURLcode ret = curl_easy_perform(hnd);using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Get, RequestUri = new Uri("https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6"), Headers = { { "Authorization", "Bearer <token>" }, },};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "net/http" "io")
func main() {
url := "https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6")) .header("Authorization", "Bearer <token>") .method("GET", HttpRequest.BodyPublishers.noBody()) .build();HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder() .url("https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6") .get() .addHeader("Authorization", "Bearer <token>") .build();
Response response = client.newCall(request).execute();import axios from 'axios';
const options = { method: 'GET', url: 'https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6', headers: {Authorization: 'Bearer <token>'}};
try { const { data } = await axios.request(options); console.log(data);} catch (error) { console.error(error);}const url = 'https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}val client = OkHttpClient()
val request = Request.Builder() .url("https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6") .get() .addHeader("Authorization", "Bearer <token>") .build()
val response = client.newCall(request).execute()use reqwest;
#[tokio::main]pub async fn main() { let url = "https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6";
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Authorization", "Bearer <token>".parse().unwrap());
let client = reqwest::Client::new(); let response = client.get(url) .headers(headers) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}curl --request GET \ --url https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6 \ --header 'Authorization: Bearer <token>'wget --quiet \ --method GET \ --header 'Authorization: Bearer <token>' \ --output-document \ - https://api.focal.inc/v1/media/uploads/f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6Poll the status of a media upload created via POST /v1/media/uploads. Returns the same shape whether the upload is still pending, finished, or failed.
Required scope: media:read. Rate-limit cost: 1.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Example
f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6Responses
Section titled “Responses”OK
object
object
The created media’s id — use it with GET /v1/media/{id} once status is finished.
pending while the URL is being fetched and stored, finished once the media is ready, failed if it could not be fetched or stored (see error).
Failure reason when status is failed. null otherwise.
The created media, once status is finished. null while pending or failed.
object
File extension.
IANA media type (the spec’s current name for what was historically called MIME type) derived from format. Provided as a convenience so clients don’t need to maintain their own extension → media-type table when uploading to ad platforms.
Don’t confuse this with the top-level type field — type is Focal’s coarse asset bucket (image/video/playable/file), while media_type is the precise IANA identifier ad platforms expect (e.g. image/jpeg, video/mp4).
Parent folder summary, or null if the media is at the space root.
object
File size in bytes.
Pixel width for visual media. null when dimensions don’t apply (e.g. type=file) or couldn’t be extracted.
Pixel height for visual media. null when dimensions don’t apply (e.g. type=file) or couldn’t be extracted.
width / height for visual media. null when dimensions don’t apply (e.g. type=file) or couldn’t be extracted.
Duration in seconds for video files. null for other types.
Total page count for multi-page formats (e.g. PDF). null for other types.
AI-generated summary of the media content, produced by Focal’s analysis pipeline. null until analysis has run (or for media types that aren’t analyzed). Read-only — for the customer-managed free-text field use description.
The media’s workflow status name (e.g. Approved), one of the space’s configured media statuses — see the Space resource’s media_statuses for the catalog. null when no status is set. Same read/write semantics as a tags attribute: reads return the name; writes accept the status id or name.
Generated identifier produced by a Creative ID naming convention — the combined <concept_id>-<variant_id> string the customer configured. null when the media isn’t governed by a Creative ID configuration.
Concept portion of the Creative ID — the stable text/date/counter prefix shared across variants. null when the media has no Creative ID.
Variant number within a concept. Incremented per variant of the same concept. null when the media has no Creative ID.
Custom-field values attached to this media — one entry per populated field. A multi-value field carries an array value; a single-value field a scalar. Empty array when no custom fields are populated.
object
The kind of custom field this value belongs to. Combined with multiple, tells you the JSON type of value (for a multi-value field it’s the type of each array element):
number→ JSONnumbercheckbox→ JSONtrue/falsedate→ ISO 8601stringtags→ option idstring(option names indisplay_value)user→ user idstring(display names indisplay_value)- everything else → JSON
string(e.g. URLs forlink)
Whether this field holds multiple values. When true, value is an array (one element per stored value); when false, value is a single scalar.
External identifiers for the ad platforms this media has been successfully exported to (YouTube, Meta, TikTok). One entry per export — re-exports and separate destinations each add an entry, so a media can appear more than once for the same channel. Empty array when the media hasn’t been exported, or only to channels without creative identifiers (e.g. Google Drive). In-progress and failed exports are excluded.
object
YouTube video id — the watch id.
Canonical YouTube watch URL.
Id of the YouTube channel the video was uploaded to (e.g. UC…).
UTC ISO timestamp of when this export was created.
object
Meta ad video id.
Meta ad account the asset was uploaded to, in the act_<id> form the Marketing API expects. null for Business Library exports, which target a business asset library rather than an ad account.
UTC ISO timestamp of when this export was created.
object
Meta ad image id.
Meta image hash — the value you reference in an ad creative’s image_hash field.
Meta ad account the asset was uploaded to, in the act_<id> form the Marketing API expects. null for Business Library exports, which target a business asset library rather than an ad account.
UTC ISO timestamp of when this export was created.
object
TikTok video id.
TikTok material id, referenced when assembling TikTok ad creatives. null until TikTok finishes processing the asset.
TikTok asset preview URL, once available. null until TikTok finishes processing the asset.
UTC ISO timestamp of when this export was created.
object
TikTok image id.
TikTok material id, referenced when assembling TikTok ad creatives. null until TikTok finishes processing the asset.
TikTok asset preview URL, once available. null until TikTok finishes processing the asset.
UTC ISO timestamp of when this export was created.
Ad-platform performance for this media, rolled up by Focal’s metrics sync across every ad it has run in — where it ran (ad_accounts / campaigns / ad_sets), when it was last live (last_activity), and its lifetime totals (insights). null for media that has never run as an ad, or when the organization has no ad accounts connected. Use the metrics request parameter to choose which metrics come back.
object
The most recent date the ad platforms reported any data for this media — i.e. when it was last live. null when no dated data has been synced.
Ad accounts this media has been running in.
object
Focal’s id for this ad account / campaign / ad set.
Its name as reported by the ad platform. null when the sync hasn’t recorded one.
Campaigns this media has been running in.
object
Focal’s id for this ad account / campaign / ad set.
Its name as reported by the ad platform. null when the sync hasn’t recorded one.
Ad sets this media has been running in.
object
Focal’s id for this ad account / campaign / ad set.
Its name as reported by the ad platform. null when the sync hasn’t recorded one.
Lifetime performance totals, one entry per metric. Which metrics appear is controlled by the metrics request parameter — by default the core set, and only those a media actually has data for. Core metrics come first, then the rest alphabetically.
object
The metric key — a core name (spend, impressions, clicks, reach, installs, watch_min_3_seconds) or a platform-specific one (e.g. meta.omni_purchase). Pass these to the metrics parameter to select them.
The metric’s total across every ad this media has run in, over all time — not a windowed figure. Currency amounts are in the ad account’s own currency.
Browser URL that opens this media in the Focal web app (its space’s library). Hand this to users as a clickable link to the exact asset.
Direct uploads only, and only in the creation response: the Google Cloud Storage session URL to send the file’s bytes to (see POST /v1/media/uploads). null everywhere else — including status polls, so a lost URL can’t be recovered; create a new upload instead.
Example
{ "data": { "id": "f216c4fe-23b1-4cc4-9b18-0f8b0c9f0da6", "status": "pending", "error": null, "media": { "id": "786beeda-55ac-4025-bf6e-43b2918e84a9", "name": "spring-hero-4x5.mp4", "type": "video", "format": "mp4", "media_type": "video/mp4", "organization_id": "b46d9033-24b2-4441-87ad-2151088fde01", "space_id": "6916bc3d-e1ab-425b-ac25-eefada01a9a2", "folder": { "id": "80539d38-3a8e-48f8-929b-8c0e9a22ab43", "name": "Spring 2027" }, "size": 48213504, "width": 1080, "height": 1350, "aspect_ratio": 0.8, "duration": 18.5, "page_count": null, "description": "Hero cut for Meta and TikTok placements, 4:5.", "summary": "A trail runner crests a ridge at sunrise in Alder & Pine gear, then the logo and Spring 2027 title card appear.", "status": "Approved", "creative_id": "SPR27-0042-3", "concept_id": "SPR27-0042", "variant_id": 3, "attributes": [ { "attribute_id": "0f35c2a2-a71a-4423-b1f2-3b5d795c6332", "attribute_name": "Usage rights", "attribute_type": "text", "multiple": false, "value": "Paid social only, 12 months", "display_value": "Paid social only, 12 months" } ], "exports": [ { "kind": "youtube", "type": "video", "video_id": "u8Lk2VnTq4c", "url": "https://www.youtube.com/watch?v=u8Lk2VnTq4c", "youtube_channel": "UCa1Dp9Qw3nXr7LbT5vHkZ2g", "exported_at": "2027-03-05T09:30:00Z" } ], "metrics": { "last_activity": "2027-03-14T00:00:00Z", "ad_accounts": [ { "id": "act_2048117733", "name": "Alder & Pine – Paid Social" } ], "campaigns": [ { "id": "120211482993410331", "name": "Spring 2027 – Prospecting" } ], "ad_sets": [ { "id": "120211482993520447", "name": "Trail runners 25–44" } ], "insights": [ { "metric": "spend", "value": 1842.5 }, { "metric": "impressions", "value": 412930 }, { "metric": "clicks", "value": 9318 } ] }, "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?file=786beeda-55ac-4025-bf6e-43b2918e84a9&folder=80539d38-3a8e-48f8-929b-8c0e9a22ab43" }, "upload_url": "https://storage.googleapis.com/upload/storage/v1/b/focal-uploads/o?uploadType=resumable&upload_id=ABPtcPqk7Zr2vN8", "created_at": "2027-03-04T10:15:00Z", "updated_at": "2027-03-06T14:42:00Z" }}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" }}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.