Scope: the public GraphQL API of a Frontify instance (DAM, guidelines, templates, workflows), its OAuth/Service-App authentication, webhooks, the URL-based Media Processing API (CDN) and the embeddable JavaScript widgets. Browser-side extension SDKs (Brand SDK: content blocks, apps, themes) and the hosted MCP server are summarised here and detailed in Integrations.
At a glance
https://{tenant-domain}/graphql — every customer has its own domain, which may be a custom domain rather than a *.frontify.com subdomain S32. OAuth: https://{tenant-domain}/api/oauth/{authorize|accesstoken|refresh|revoke} S8. Developer tokens UI: https://{tenant-domain}/oauth-access-token S8 S74. CDN: https://media.ffycdn.net/…?mod=v1/{transformations} S57. MCP: https://mcp.frontify-integrations.com/mcp[/packs/{pack}] S66 S67MetadataField/metadataValues operations → CustomMetadata (removal date 2026-07-01 in the schema), project(id) query → node(id), manual uploadFile.chunkSize (2026-01-01), long-lived developer tokens for production (January 2026) S34 S62expires_in: 2592000 (30 days) and refresh_token: null — request a new token when it expires S8. OAuth access tokens: "between one week and three months, depending on configuration"; the lifetime is configurable per instance S8. Refresh via /api/oauth/refresh: returns a new access + refresh token, invalidates the old access token; refresh tokens are single-use S8. Developer tokens: 7 d, 30 d, 90 d, 6 months or 1 year; tokens issued before January 2026 carry a 10-year expiry S8 S74basic:read, basic:write, account:read, webhook:read, webhook:write (finder:read, blocks:read, blocks:write exist but are ignored for the API) S11; schema enum SecurityScope has the same 5 values S62. Every response carries x-oauth-scopes S11. Scopes only cap what a token could do; actual rights come from the user behind the token (Viewer/Editor/Owner roles per library/project/guideline, account settings), exposed as boolean currentUserPermissions.can* fields on major entities S7 S10extensions.complexityScore S28 S31 S65. Whether the limit is per token, user or account is Not publicAuthentication
Which mode to use S8
| Mode | For | Credentials | Notes |
|---|---|---|---|
| Service App | Production machine-to-machine | client_id + client_secret → POST/api/oauth/accesstoken with grant_type=client_credentials, client_id, client_secret, scopes (subset of the app's scopes) | Beta. Runs with the permissions of the owning user (Frontify suggests a dedicated service user). Created with the createServiceApp mutation (requires X-Frontify-Beta: enabled, typically authenticated with a developer token); the secret is shown once; listed via serviceApps, removed via deleteServiceApp S9 S17 S62 |
| OAuth 2 authorization code | Apps acting on behalf of each user | OAuth client created by an account admin in the account's application settings: confidential on/off, redirect URL, allowed scopes; a client is valid only for the account where it was created (multi-account clients via developer@frontify.com) S9 | PKCE (S256, verifier 43–128 chars) mandatory for public clients; the Frontify Authenticator library implements it S8 S45 |
| Developer token | Local development, CI, Service-App setup | Created by any user at /oauth-access-token: name, description, scopes (immutable), expiry | Not for production since January 2026 S8 S34 S74 |
Authorization-code flow S8
- Redirect the user to GET
https://{tenant-domain}/api/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=basic:read%20basic:write&state=…[&code_challenge=…&code_challenge_method=S256]. At leastbasic:readis required; scopes must be enabled on the client. The user sees a consent screen listing the scopes. - Frontify redirects back with the authorization code and
state(verifystateagainst CSRF). Unverified The page names the parameterauthorization_codein its example redirect URL butcodeelsewhere — check both. - POST
https://{tenant-domain}/api/oauth/accesstoken(JSON or multipart) withgrant_type=authorization_code,code,client_id,redirect_uri,scope(+-separated), plusclient_secret(confidential) orcode_verifier(PKCE) →{token_type: "Bearer", expires_in, access_token, refresh_token}. - Refresh: POST
/api/oauth/refreshwithgrant_type=refresh_token,refresh_token,client_id,scope(+client_secretif confidential). - Revoke: POST
/api/oauth/revokewithtokenand optionaltoken_type_hint→ HTTP 200.
Multi-tenancy: clients must know the customer's domain; the Authenticator lets the app fix it or lets the user type it, and returns it in bearerToken.domain S45 S46. OpenID discovery (/.well-known/openid-configuration) is not served on instances S65.
Core resources
All operations are fields on RootQuery / RootMutation of POST/graphql. "β" = flagged beta in the legacy reference (needs X-Frontify-Beta: enabled) S63. Domain grouping follows the official reference S62.
| Resource | Queries / mutations | Notes |
|---|---|---|
| Entry points | currentUser, account, node(id) (any object by global ID), brands, brand(id) | account needs account:read and an account owner S3 S62 |
| Assets | asset(id), assets(ids); createAsset, createExternalAsset, updateAsset, updateAssets β, replaceAsset, moveAssets, deleteAsset (returns ID only since 2024-01-01), add/removeAssetPreviewImage, add/removeAssetRelations, add/remove/syncAssetTags | S62 S34 |
| Variants & attachments | createAssetVariant, replaceAssetVariant, deleteAssetVariant; createAttachment, deleteAttachment | Variants exist only in Logo and Icon libraries S41 S62 |
| Uploads | uploadFile → file ID + presigned S3 part URLs | See Uploads S19 |
| Libraries & workspace projects | library(id) (Media, Icon, Logo, Document libraries), workspaceProject(id); createWorkspaceProject; project(id) deprecated | Libraries cannot be created via the API S21 S62 S34 |
| Folders | createFolder, updateFolder, moveFolders, deleteFolders; browse via Library.browse.folders | S62 S34 |
| Collections | createCollection, updateCollection, deleteCollection, add/remove/setCollectionAssets; library.collections, node … on Collection (searchable since 2025-09) | S34 S62 |
| Metadata schema | createCustomMetadataProperty, updateCustomMetadataProperty, deleteCustomMetadataProperty, add/removeCustomMetadataPropertyOptions; read Library.customMetadataProperties | Properties are defined per library S25. Legacy createMetadataField/deleteMetadataField deprecated S34 |
| Metadata values | addCustomMetadata, removeCustomMetadata, setCustomMetadata β (bulk: many assets × many properties); read Asset.customMetadata | Legacy addAssetMetadataFieldValue/removeMetadataValue deprecated S25 S34 |
| Search | Library.assets(query:…), Library.searchAssets(input:…), WorkspaceProject.assets, brand.search, account search | S20 S62 |
| Rights | Licenses: createLicense, deleteLicense, add/removeAssetLicense; copyright on Asset.copyright; availability window (availability, expiresAt, beginsAt input); targets: add/set/removeLibraryAssetTargets β | Availability GA since April 2026; targets require targets enabled on the library and all assets in one library per call S34 S62 |
| Workflows | createWorkflowStatus β, update/deleteWorkflowStatus β, createAssetWorkflowTask β, moveWorkflowTask β, update/deleteWorkflowTask β, assignees and checklist items/presets β | One task (hence one status) per asset S23 S63 |
| Comments | createAssetComment, replyToComment, editComment, deleteComment, resolveAssetComment, reopenAssetComment; read Asset.comments | S62 |
| Users & collaborators | currentUser, account users (admin), library.collaborators.users.edges { role node }, inviteProjectUser | S34 S62 |
| Brands | createBrand β, updateBrand β, deleteBrand β | S34 S63 |
| Brand essentials | brand.essentials → colour palettes/colours (HEX, RGBA, CMYK variants, Pantone…) and font families/styles | Beta S29 |
| Guidelines (brand portal content) | guidelinePage β, guidelinePageBlock β, guidelineNavigationItem β; ~40 mutations to create/update/move/publish/hide documents, pages, sections, headings, blocks β | Content API in progress S28 S63 |
| Creative templates | creativeTemplate(id), creativeExport(id); exportCreative, cancelExportCreatives | Asynchronous render jobs; requires a Templates licence S26 S34 |
| Catalogs (structured business data) | catalog β, catalogSelection β; create/update/remove catalogs, columns, rows, cells, ingestCatalogRows β | S18 S63 |
| Webhooks | webhooks(limit, page); configureProjectWebhook (v2), installProjectWebhook (v1), uninstallWebhook | S36 |
| Service apps | serviceApps β; createServiceApp β, deleteServiceApp β | S17 S63 |
| Marketplace | publishMarketplaceApp | S62 |
| Renditions | No rendition resource; URLs are fields on asset types (previewUrl, thumbnailUrl, downloadUrl) + CDN transformations | S24 S57 |
Data model
- Hierarchy: everything belongs to an Account; an account holds Users and Brands; a brand holds Libraries (finalised, widely consumable assets), Workspace projects (called Projects in the UI: work-in-progress, collaboration) and Guidelines (pages) S3. Library types:
MEDIA_LIBRARY,ICON_LIBRARY,LOGO_LIBRARY,DOCUMENT_LIBRARYS62; icon libraries accept SVG only S19. - Identifiers: global IDs are Base64-encoded JSON
{"identifier": <int>, "type": "<type>"}(so they start withey…); some queries still accept the numeric ID from UI URLs, but that is slated for deprecation S30.externalId(≤ 255 characters) stores a foreign key on assets; it is not unique S19 S22. - Asset vs file: a File is an uploaded binary that can back an Asset, an Attachment, a revision or a font; asset creation is separate from upload S3 S19.
- Asset types (interface
Asset):Image,Video,Audio,Document,File(fallback when unclassified or processing failed),EmbeddedContent(external URL assets) S4 S62. Classification by extension after asynchronous processing (e.g. Image: bmp, dng, eps, gif, jpg, png, psd/psb, svg, tif, webp, heif…; Video: avi, mkv, mov, mp4, webm…; Document: docx, pptx, xlsx…) S4;.aifiles became Documents on 2026-05-06 S34. - Common asset fields:
title,description,alternativeText,isDecorative(images),author,tags(source: MANUAL or AUTO for AI tags),copyright,licenses,availability {from,to},expiresAt,status(PROCESSING,FINISHED,PROCESSING_FAILED),customMetadata,workflowTask,location(folder breadcrumb),relatedAssets,revisions,variants,attachments,comments,targets,createdAt/modifiedAt,fileCreatedAtS62 S34. Images addwidth,height,focalPoint,previewBackgroundColor, and a betaprovenanceobject (content-credential state, content origin, generator, signing certificate) S62. - Custom metadata: property types
TEXT,LONGTEXT,NUMBER,DATE(YYYY-MM-DD),URL,SELECT,MULTISELECT,CATALOGS62 S25; options have Base64 IDs; properties can be required (assets missing a required value stay hidden from end users), searchable/viewable, conditionally dependent (FILLED,SELECT_EQUALS,SELECT_ONE_OF) and permissioned (VIEW/EDIT/ADMINper role, user or group) — the advanced settings are beta S25 S34. - Revisions:
asset.revisions(id, dates, creator) since 2026-06; no file/URL info per revision yet S34.
Search & querying
| Mechanism | Syntax | Limits / notes |
|---|---|---|
Library.assets / WorkspaceProject.assets (AssetQueryInput) | query: {search, externalId, types, inFolder, sortBy, filter: {andConditions[], orConditions[]}}; condition types TAG, CUSTOM_METADATA_VALUE, EXTERNAL_ID, FILE_EXTENSION, operator IS / IS_NOT | Term search is case-insensitive with partial matching over title, OCR text (if enabled), description, tags, legal and custom metadata; exact phrase with escaped quotes; AND / OR / NOT operators; results capped at 10,000 — paginate without a query to list everything S20 S34 |
Library.searchAssets (since 2026-09) | input: {searchTerm, filters[], folderId, isRecursive, sortBy {key, direction}, cursor, limit}; typed filters on availability, dates, collections, copyright, creator, custom metadata value, extension, "has description", "missing metadata", download protection, licenses, orientation, tags, targets, workflow status | Filters are AND-only; no cap without searchTerm, 10,000 cap with it; sort keys AVAILABLE_FROM, AVAILABLE_UNTIL, CREATED, FILE_CREATION_DATE, FILE_EXTENSION, LAST_MODIFIED, MANUAL, RELEVANCE, TITLE; select values are matched on option text, not ID; index lag of a few minutes; pages may return fewer items than requested; LAST_MODIFIED sort can skip/duplicate items if assets change between pages S20 |
| Brand / account search | brand.search(query: {term, filter: {sources: LIBRARIES | WORKSPACE_PROJECTS | LIBRARY_PAGES}}) | Brand-level search is beta; results depend on the user's permissions (ranking can differ per user) S20 S34 |
| Collection search | assets(query: {term}) on a collection | Since 2025-09 S34 |
Uploads & binaries
uploadFile(input: {filename, size})→{id, urls[]}: a temporary file ID and presigned S3 part URLs. Part count is computed from the size: max 1,000 parts, files < 15 MB in one part, otherwise 15 MB–5 GB parts; maximum file size 5 TB (the guide itself says "no limit") Unverified. SupplyingchunkSizemanually is deprecated since 2026-01-01 S34.PUTeach chunk (ceil(size / number_of_urls)bytes) as the raw body to its URL — no Frontify token on these calls S19.- Use the file ID once:
createAsset(input: {fileId, parentId, title, description, alternativeText, externalId, tags, copyright, availability, expiresAt, author, skipFileMetadata, previewBackgroundColor})→job { assetId };parentIdis the Base64 ID of a library, workspace project or folder. The same file ID can instead feedreplaceAsset(new revision),createAttachmentor variant mutations S19 S22 S62.skipFileMetadata: trueignores embedded EXIF/XMP S34.
- Executables (exe, dmg, sh, bat, cmd…) are rejected S19. After creation the asset exists immediately but is
PROCESSING; previews and type classification follow asynchronously, normally seconds to a minute, up to 15 minutes worst case S4. - Bulk migrations: Frontify's own guide recommends upload + create + custom metadata per file, keyed by
externalId, withaccount:read basic:read basic:write; libraries must be pre-created in the UI S21.
Downloads & renditions S24 S57
previewUrl(width, height): CDN URL for a specific revision; never expires, stays valid even if the asset is later changed or deleted; accepts Media Processing parameters S57 S62.thumbnailUrl: custom thumbnail, falling back to the preview S24.dynamicPreviewUrl(beta, needsX-Frontify-Beta: enabledandX-Frontify-Development-Flags: PUBLIC_API_DYNAMIC_CDN): stable URL always serving the latest revision; 1-year CDN TTL with near-real-time invalidation; stops working when the asset is deleted S24 S57. Not present in the published SDL S62.downloadUrl(validityInDays, permanent): original file;nullwhen download-protected for the user (checkcurrentUserPermissions.canDownload);permanent: true→ non-expiring URL always pointing to the latest version; otherwise a version-pinned expiring URL S24 S10. Unverified Default validity is stated as 7 days S24, 1 day (January 2023 changelog) and 6 days with a 1–6 day range (2022 changelog) S34 — passvalidityInDaysexplicitly.- Media Processing API: append
?mod=v1/<op>/<op>…to a preview or dynamic URL; chained operations includeresize,cover,contain,inside,crop(with coordinates),focus(incl.focus=auto),refit-cover/refit-inside(subject-aware),background(incl.background=remove),colorize,flip,quality/quality-min/quality-max,output=maincolor|meancolor|preview|blank,download=, colour-vision simulations; output capped at 4096 px, metadata stripped, original not retrievable through the CDN; WebP/next-gen formats chosen by content negotiation; PDFs rendered as first-page image inside<img>or served whole when linked S56 S57 S59. Delivered through AWS CloudFront, decoupled from the application (keeps serving if the app is down) S58.
Events & webhooks
- Managed only through GraphQL:
configureProjectWebhook(input: {projectId, notificationUrl, name, version: 2, subscribeTo?: [AssetWebhookEvent]})(v2; omitsubscribeTofor all events),installProjectWebhook(v1),webhooks(limit, page)to list,uninstallWebhook(input: {id})(returns only the ID since 2026-09-18). - Scope: one library or workspace project (
projectId). Requireswebhook:write+basic:writeand Editor rights on that location; reading requireswebhook:read. - The subscribed event list cannot be queried back after creation — store it yourself S36.
- Response to install includes the webhook
secretS36.
Events (v2, enum AssetWebhookEvent) S41 S62: ASSET_CREATED, ASSET_UPDATED (generic: title, description, alt text, decorative flag, legal fields, download protection/sizes, availability, document category, main logo, preview colour…), ASSET_DELETED, ASSET_PROCESSED (conversion finished), ASSET_MOVED, ASSET_REVISION_ADDED, ASSET_TAG_ADDED/REMOVED, ASSET_CUSTOM_METADATA_UPDATED, ASSET_COLLECTION_ADDED/REMOVED, ASSET_LICENCE_ADDED/REMOVED, ASSET_RELATION_ADDED/REMOVED, ASSET_ATTACHMENT_ADDED/REMOVED, ASSET_VARIANT_ADDED/REMOVED/UPDATED, ASSET_TARGET_CHANGED, WORKFLOW_TASK_ASSIGNED/MOVED/DELETED. Delivered action names are dotted (asset.revision.added, asset.workflow_task.moved…). v1 only has ASSET_CREATE, ASSET_UPDATE, ASSET_DELETE S42. Unverified The v2 changelog announces "23" events; the schema enum lists 24.
- Creating an asset fires created + updated + processed; a new revision fires updated, revision.added, updated, processed S41.
Payload S40: {event: {action, occurredAt, processedAt, sender: {id} | null, payload: [{assetId…}]}, meta: {accountId, webhookId, isSampleEvent}}. Minimal data only — fetch details via GraphQL S36. Bulk actions and same-user/same-action events are bundled into one request S35 S40. accountId + webhookId is globally unique (use it to pick the verification secret) S40.
Delivery & security S37 S38 S39
- HTTPS endpoint with a valid TLS certificate; disable CSRF protection on the receiver.
- Signature header
X-Frontify-Webhook-Signature= Base64 of HMAC-SHA256(raw body, webhook secret); compare in constant time. - Respond 2xx within 5 seconds; redirects are not followed (treated as failure).
- Retries: up to 10, exponential (1 s, 4 s, 16 s, 1 min 4 s, 4 min 16 s, 17 min 4 s, 1 h 8 min, 4 h 33 min, 18 h 12 min, ~3 days); after the last failure the webhook is automatically uninstalled.
- No ordering guarantee; duplicates possible — make processing idempotent.
Other event sources: in-product Automations (triggers → conditions → actions, beta) S112; the Zapier and Tray.io connectors S135 S136. No event stream for guideline or template changes (webhooks cover assets only) S35.
Limits & quirks
| Topic | Detail | |
|---|---|---|
| HTTP status for GraphQL errors | Error-handling page: validation/permission/business errors return HTTP 200 with an errors[] array (categories graphql, validation, permission, internal/businessLogic) and possible partial data; rate-limit page: GraphQL errors return HTTP 422. An anonymous call returned 200 + permission error | S31 S12 S65 Unverified contradiction — handle both |
| Error-rate abuse | Repeated erroneous requests can be flagged as suspicious; limit retries of failing calls | S12 |
| Auth errors | Expired/revoked/invalid token → HTTP 401 | S31 |
| Beta header | X-Frontify-Beta: enabled (case-sensitive value) adds beta members to the schema; some features also need X-Frontify-Development-Flags (e.g. PUBLIC_API_DYNAMIC_CDN, PUBLIC_API_EXTERNAL_DATA_SOURCE_FILE) | S15 S24 S27 |
| Plan gating | "Some API features may be restricted based on your current Frontify plan"; the Creative API needs a Templates licence; introspection must be enabled on the account for the Explorer | S13 S14 S34 |
| Libraries & archiving | Libraries cannot be created and workspace projects cannot be archived via the API (isArchived is read-only) | S21 S34 |
| Targets | Changelog: asset targets queryable/manageable (beta); asset-events page: "targets cannot be accessed or modified via the public API" | S34 S41 Unverified contradiction (the schema has the target mutations S62) |
| externalId | Not unique; no version information — Frontify suggests a version tag (e.g. version-{hash}) for sync | S22 |
| Custom-metadata filters | searchAssets filters select values by option text, dates as YYYY-MM-DD; mutations use option IDs | S20 S25 |
| Lifecycle dates | No server validation that beginsAt < expiresAt | S34 |
| Templates API coverage | Digital & Print templates only partially supported; the external data source ID must be found manually in browser dev tools | S27 |
| Guideline content | Split large reads (guidelines → pages → blocks) to stay under the complexity cap; pages list max 100 per call | S28 |
| Field renames | Frontify recommends GraphQL aliases to absorb renamed fields | S175 |
| Response header | Instances return X-Frontify-Version (application build) | S65 |
SDKs, tools & code samples
| Library / tool | Language | Maintainer | Status | |
|---|---|---|---|---|
| GraphQL server SDK | — | — | None published (no Frontify package on PyPI/Packagist; npm scope holds front-end packages only) | S69 |
@frontify/frontify-finder | TypeScript | Frontify | 3.0.5 (2026-09-13); embeddable asset picker handling auth and UI; migration guide to Finder v3 | S44 S69 |
@frontify/frontify-authenticator | TypeScript | Frontify | 2.1.0 (2026-09-23); OAuth 2 + PKCE pop-up flow, WebCrypto, TLS domain required | S45 S69 |
Brand SDK: @frontify/app-bridge / app-bridge-app / app-bridge-theme, @frontify/guideline-blocks-settings, @frontify/frontify-cli | TypeScript/React | Frontify (Frontify/brand-sdk) | app-bridge 3.12.10, app-bridge-app 0.3.0, app-bridge-theme 1.3.0, CLI 6.2.7 (2026-09) | S48 S68 S69 |
@frontify/fondue | React | Frontify | Design system for extensions, 13.7.10 | S69 |
| GraphQL Explorer | Web (GraphiQL) | Frontify | OAuth sign-in to own instance; "Beta features" toggle | S14 S64 |
| Frontify MCP server (hosted) | — | Frontify | Wraps the GraphQL API in tool packs; OAuth | S67 S66 |
| Connector sources (Contentful, Sanity, AEM, Sketch…) | TS/Java | Frontify (GitHub) | MIT-licensed repos, several updated 2026 | S68 |
brix-ag/graphql-client-frontify | Java | Community | Apache-2.0, pushed 2026-09-25 | S70 |
pyotrli/frontify_api_example | Python | Community | Example script, pushed 2024-10-21 | S71 |
| Python example scripts (GraphQL client, auth, Base64 ID helpers, asset/tag examples) | Python | Frontify team / community | Linked from the developer portal | S33 |
Changelog of the API
| Date | Change | |
|---|---|---|
| 2026-09-18 | uninstallWebhook returns the webhook ID only | S34 |
| 2026-09-14 | Breaking (outside the usual schedule): CreativeTemplate.variables.allowedColorValues.id numeric → string | S34 |
| 2026-09 | Library.searchAssets (typed filters, cursor pagination); isDecorative out of beta | S34 |
| 2026-07-01 | Scheduled removal of legacy metadataValues field (per schema deprecation reason) | S62 |
| 2026-06 | asset.revisions query | S34 |
| 2026-05-06 | .ai files classified as Document instead of Image | S34 |
| 2026-04 | beginsAt on updateAsset; asset availability GA | S34 |
| 2026-01 | Developer tokens restricted to testing, expiry 7 days–1 year; Service Apps (client credentials) recommended for production | S34 S8 |
| 2026-01-01 | uploadFile.chunkSize no longer settable (dynamic part sizing) | S34 |
| 2025-09-01 | Webhooks v2: granular asset events and per-event subscription | S42 |
| 2025-09 | Search within collections | S34 |
| 2025-07-01 | Legacy MetadataField/Value operations deprecated (extended removal period); user.email/user.avatar, Library.assetCount, LibraryCollection.isPrivate, SubFolder removals | S34 |
| 2025-05 | Exact-match and OCR search | S34 |
| 2025-04 | Alternative text; Creative API (template exports); fileCreatedAt | S34 |
| 2025-01-01 | externalProducts on assets removed | S34 |
| 2024-06 | Asset location GA; skipFileMetadata | S34 |
| 2024-03 | thumbnailUrl | S34 |
| 2024-01-01 | deleteAsset returns ID only | S34 |
| 2023-07-01 | createAsset.projectId → parentId; brand search searchIn → filter.sources | S34 |
| 2023-01 | Permanent downloadUrl option; brand.color → brand.rgbaColor | S34 |
| 2022-12 | Collections GA | S34 |
| 2022-03-07 | Libraries and workspace projects separated; brand.projects and project(id) deprecated | S34 |
| 2022-01-01 | downloadUrl becomes a signed, expiring, nullable URL | S34 |
| 2020-12-30 | snake_case fields replaced by camelCase | S34 |