ffy-api-cli
v0.2.1
Published
Agent-friendly CLI for the Frontify GraphQL API
Maintainers
Readme
Frontify CLI
Agent-friendly CLI for the Frontify GraphQL API.
Run with npx
After publishing to npm:
npx ffy-api-cli --help
npx ffy-api-cli auth login your-company.frontify.com
npx ffy-api-cli status
npx ffy-api-cli brand list --jsonThe npm package is named ffy-api-cli; the executable inside it is named
frontify. The shorter package name frontify is already taken on npm.
Install locally
npm install
npm linkDuring development you can also run:
node ./bin/frontify.mjs --helpSign in
Create an OAuth app in Frontify with a redirect URL like:
http://localhost:5600/oauthThen run:
frontify auth login --domain your-company.frontify.comYou can also pass the instance as a positional value:
frontify auth login your-company.frontify.comfrontify status is a shortcut for frontify auth status.
The login flow uses Frontify's /api/oauth/random PKCE challenge by default,
matching the existing Frontify CLI OAuth behavior. Token exchange is sent as
JSON, which is what Frontify's OAuth endpoint expects. If you are testing a
fully standard local PKCE OAuth app instead, add --challenge local.
By default, OAuth login now matches the official Frontify CLI:
client_id=block-cli
redirect_uri=http://localhost:5600/oauth
scope=basic:read+blocks:read+blocks:writeThe official block-cli client is not allowed to issue basic:write. For
general GraphQL writes, use a dedicated OAuth client registered for
http://localhost:5600/oauth, or store a developer/service token.
OAuth client IDs are bound to their registered redirect URI. For example,
frontify-explorer is used by Frontify's hosted Authenticator flow with a
callback like /connection/authenticator, so it cannot complete a localhost CLI
login unless that client is also configured for the CLI redirect URL. For that
case, copy the access token from the hosted flow and store it with
frontify auth set-token.
For general GraphQL write access through OAuth:
frontify auth login \
--domain your-company.frontify.com \
--client-id YOUR_GRAPHQL_CLI_CLIENT_ID \
--scopes basic:read,basic:writeIf your OAuth app requires a client secret:
frontify auth login \
--domain your-company.frontify.com \
--client-id YOUR_CLIENT_ID \
--client-secret YOUR_CLIENT_SECRET \
--scopes basic:read,basic:writeThe CLI stores tokens in macOS Keychain when available. If Keychain is not
available, it falls back to a chmod 0600 file under the CLI config directory.
The raw token is not printed by default.
Service or developer token
For automation, you can store an existing Frontify token without OAuth:
pbpaste | frontify auth set-token \
--domain your-company.frontify.com \
--token-stdin \
--scopes basic:read,basic:writeAgent usage
Once signed in, agents can call the CLI on your behalf:
frontify whoami --json
frontify gql --query 'query { currentUser { id name } account { id } }'
frontify gql ./query.graphql --vars ./vars.jsonThe account GraphQL query requires account:read in addition to basic:read.
If frontify api run account returns a missing-scope error, sign in with:
frontify auth login your-company.frontify.com \
--scopes basic:read,account:read,blocks:read,blocks:writeIf the OAuth client rejects account:read, use a Frontify OAuth client allowed
to request that scope or store a developer/service token with auth set-token.
The generated API layer covers the documented GraphQL reference:
frontify api list
frontify api list --kind mutation
frontify api list --json
frontify api --list
frontify api help createAsset --json
frontify api help createAsset --json --related
frontify api type RemoveCollectionAssetsInput --json
frontify api query --list
frontify api query --list --verbose
frontify api query brandEssentialsFonts
frontify api query asset
frontify api query createAsset
frontify api query brandEssentialsFonts --json
frontify api run currentUser
frontify api run webhooks --all --limit 100
frontify api run updateAsset \
--input '{"id":"asset-id","data":{"title":"New title"}}' \
--confirm
frontify api run updateAsset \
--input '{"id":"asset-id","data":{"title":"New title"}}' \
--dry-runMutations through api run require --confirm unless --dry-run is used.
Use api help <operation> --json to inspect operation arguments and defaults,
api type <TypeName> --json to inspect a focused GraphQL object, interface,
enum, or input object shape, and api help <operation> --json --related only
when you explicitly want the recursive related-type payload. api run
validates nested required input fields before sending the request.
Use api query <operation> to print the generated GraphQL document for any
documented query or mutation without making a network request. Interface and
union selections include inline fragments, for example ... on Image for
asset. Curated templates use lower camel case names such as
brandEssentialsFonts. The old PascalCase template names remain aliases.
api query --list prints a names-only index; add
--verbose for descriptions or --json for structured metadata.
All GraphQL requests include X-Frontify-Beta: enabled so beta/reference
fields such as guideline pages and blocks are available when the tenant
supports them.
Human commands
For common workflows, use domain-specific commands:
frontify asset get ey123
frontify asset create library123 ./logo.png
frontify asset create library123 ./hero.jpg --title "Campaign hero" --tags "campaign,approved"
frontify asset create library123 ./hero.jpg --directory "Campaign/Launch"
frontify library upload library123 ./logo.png ./banner.jpg
frontify asset comments ey123 --status open --all
frontify asset comment ey123 --content "Looks good"
frontify brand list
frontify brand find sbb
frontify brand libraries brand-check --search sbb
frontify brand essentials brand-check
frontify brand colors brand-check
frontify brand fonts brand-check
frontify brand templates dynamo --search business --json
frontify brand templates pizzapie --all --output templates.json
frontify brand guidelines brand-check --language EN --search logo
frontify brand guideline-search brand-check "logo usage" --limit 20
frontify guideline pages guideline123 --search logo --all
frontify guideline search guideline123 "logo usage" --format jsonl --output guideline-search.jsonl
frontify guideline page page123 --json
frontify guideline block block123 --json
frontify download asset asset123 --output downloads/assets
frontify download library library123 --output downloads/my-library
frontify download folder folder123 --output downloads/my-folder
frontify download guideline guideline123 --output downloads/my-guideline
frontify library assets library123 --search logo --all
frontify library comments library123 --status all --latest 10 --all-comment-pages
frontify library comments library123 --latest 100 --format jsonl --output comments.jsonl
frontify comment reply c123 --content "Updated"
frontify comment resolve c123
frontify folder create parent123 --name "Campaign"
frontify collection add-assets collection123 asset1 asset2
frontify metadata set asset1 --property property1 --value '"Approved"' --confirm
frontify webhook list
frontify webhook list --allHuman commands print readable tables in a terminal. Add --json for machine-readable output.
Asset reads:
asset getandlibrary assetsquery concrete Frontify asset types by default: Image, Video, Document, File, and Audio.- Type-specific fields are selected with GraphQL inline fragments, including
filename,size,extension,fileCreatedAt,previewUrl,thumbnailUrl, anddownloadUrlwhere available. - Media-specific stable fields are included when applicable, such as image/video dimensions, video duration/bitrate, document page count, and focal point.
- Asset reads also include stable shared fields such as tags, current user permissions, and custom metadata values.
- Comment reads include markings, mentioned users, current user permissions, modifiers, and replies.
- Signed
downloadUrlvalues can be verbose and temporary; use--fieldsor--outputwhen collecting lots of asset records. - Workflow task/status fields are beta and may be unavailable for a tenant/client; use explicit
gqlorapi runchecks before depending on them.
Guideline reads:
brand guidelines <brand>lists guideline projects for a brand, including default/translation languages and page counts.brand templates <brand>lists creative templates for a brand through scopedbrand(id:)queries, avoiding the over-broad nestedbrands { creativeTemplates }shape.brand essentials <brand>,brand colors <brand>, andbrand fonts <brand>read Brand Essentials color palettes and font families.- Add
--search termtobrand guidelines,guideline pages, andguideline library-pagesto filter the returned listing client-side. guideline pages <guidelineId>lists beta Guideline Pages for a guideline.guideline library-pages <guidelineId>lists legacy/current library pages exposed by the guideline.guideline page <pageId>reads page sections and content elements. It includes headings, blocks, block references, block type, searchable block content, settings, and referenced block asset previews.guideline block <blockId>reads a block directly, including content, settings, type schema, and asset settings.guideline search <guidelineId> <term>scans guideline pages, headings, block content, block type names, URLs, and settings. Use--limit,--page-limit,--max-pages,--section-limit,--section-max-pages,--element-limit,--titles-only, and--contextto bound the scan.brand guideline-search <brand> <term>searches across a brand's guidelines with the same output and adds--guideline-limit/--guideline-max-pages.download asset <assetId>saves one binary asset file.download library <libraryId>anddownload folder <folderId>save binary asset files in bulk; folders recurse by default and support--no-recursive.download guideline <guidelineId>creates a resumable local guideline export withpages.json,page-files.json,failures.json, and one JSON file per guideline page.- Download commands skip existing files unless
--overwriteis passed and support--dry-runfor previews. They print concise progress by default, such asFound library "Media"; starting download...andDownloaded 10 assets to ...; add--jsonfor the full receipt. For bulk downloads,--limit 10caps the total downloaded assets/pages and--page-size ncontrols the API request chunk size. - Search is client-side because the documented guideline GraphQL fields do not expose native text query arguments.
- Guideline page/block queries are beta and can be tenant- or token-gated; when Frontify rejects them, the CLI prints the GraphQL response unchanged and exits
2.
Large outputs:
--output <file>writes the payload to disk and prints only a small receipt to stdout.--format jsonlwrites list-like results as one JSON record per line, useful withhead,rg,wc -l, andjq.--briefprojects common records to compact fields; for comments this includes id, created time, asset title, resolved state, creator, and content.--fields a,b,cselects exact record fields. Dot paths likecreator.nameare supported.--truncate nshortens long string fields after whitespace is collapsed.
Example:
frontify library comments library123 \
--status all \
--latest 100 \
--format jsonl \
--fields createdAt,assetTitle,isResolved,creatorName,content \
--truncate 240 \
--output comments.jsonl
head -n 10 comments.jsonl
rg "red window" comments.jsonl
jq -r '.assetTitle + "\t" + .content' comments.jsonl | headAsset creation:
asset create <parentId> <file...>accepts a library, workspace project, or folder API id asparentId.library upload <libraryId> <file...>is a shortcut for the common library upload case.- The CLI initializes
uploadFile, streams each local file chunk to the returned S3 PUT URLs, then callscreateAsset. - Titles default to the filename without extension. Use
--titlefor a single file. - Useful options:
--description,--alt,--author,--external-id,--tags "a,b",--directory "Folder/Subfolder",--skip-file-metadata,--collection <collectionId>, and--dry-run. - Frontify requires
basic:writeforuploadFileandcreateAsset; the defaultblock-cliOAuth client cannot request that scope, so use a dedicated OAuth client or stored developer/service token for uploads.
Pagination:
- Use
--page n --limit nfor a single page. - Use
--allon supported paginated commands to fetch every page and mergeitems. - Use
--max-pages nas a safety cap for long scans. library commentsalways scans asset pages and supports--all-comment-pageswhen an individual asset has more comment pages.
Rate limits
Frontify currently documents a GraphQL limit of 2500 requests per 5 minute rolling window. The CLI protects paginated and repeated calls by keeping a shared rolling-window request ledger and handling HTTP 429 responses centrally:
- By default, the CLI reserves at most 2400 GraphQL requests per Frontify host in any 5 minute rolling window, leaving headroom below the published ceiling.
- The rolling-window state is shared across repeated CLI invocations for the same Frontify host, so shell loops and agent scripts do not accidentally bypass the budget.
- Optional request spacing is available with
FRONTIFY_RATE_LIMIT_MIN_INTERVAL_MS, but the safety mechanism is the rolling-window count, not the delay between requests. - HTTP 429 responses retry with
Retry-Afterwhen present, otherwise exponential backoff. - HTTP 422 GraphQL errors are not retried. Repeated GraphQL errors trigger a short local cooldown to avoid suspicious error bursts.
- HTTP 403 responses trigger a local cooldown as a conservative stop signal; retry later instead of probing.
- If the requested wait is too long, the CLI stops and returns structured
rateLimitmetadata instead of continuing to send requests.
Environment overrides:
FRONTIFY_RATE_LIMIT_REQUESTS=2400
FRONTIFY_RATE_LIMIT_WINDOW_MS=300000
FRONTIFY_RATE_LIMIT_MIN_INTERVAL_MS=0
FRONTIFY_RATE_LIMIT_SHARED=true
FRONTIFY_RATE_LIMIT_RETRIES=3
FRONTIFY_RATE_LIMIT_BASE_DELAY_MS=1000
FRONTIFY_RATE_LIMIT_MAX_WAIT_MS=60000
FRONTIFY_RATE_LIMIT_422_THRESHOLD=3
FRONTIFY_RATE_LIMIT_422_COOLDOWN_MS=60000
FRONTIFY_RATE_LIMIT_403_COOLDOWN_MS=300000Authenticated profiles use the instance GraphQL endpoint by default:
https://your-company.frontify.com/graphqlThe public developer endpoint is still useful for unauthenticated schema checks:
https://developer.frontify.com/graphqlOverride it per profile with --graphql-endpoint during login or token setup.
Commands
frontify auth login
frontify auth set-token
frontify auth status
frontify status
frontify auth set-endpoint
frontify auth logout
frontify whoami
frontify gql
frontify introspect
frontify api list
frontify api help
frontify api run
frontify asset
frontify brand
frontify library
frontify comment
frontify folder
frontify collection
frontify guideline
frontify metadata
frontify webhook