Public-key infrastructure
Flick uses public-key infrastructure and delegated credentials to provide stateless authentication for the SDK.
Your app names a trust-root URL in its root Swift scope. Flick uses the public key published for that URL to verify who can publish updates to the app. Set the URL on your existing root declaration:
import FlickSDK
// Other app declarations…
// Your app’s trust-root URL: Flick discovers its public key via HTTPS or DNS
// to verify permission to publish updates to this app.
@FlickScope(url: "https://acme-corp.com")
enum App {
// Your scopes, views, state, and actions…
}When you publish a Flick under this trust root, Flick verifies that the root authority authorized the publication before accepting it. It first looks for the root public key at https://acme-corp.com/.well-known/flick. That endpoint serves a JSON array binding the trust-root URL to your app’s bundle ID and public key:
[
{
"iss": "https://acme-corp.com",
"sub": "com.acme.myapp",
"alg": "ES256",
"kid": "<root public key>"
}
]Here, iss is the trust-root URL, sub is the bundle ID, and kid is the Base58-encoded root public key. alg identifies the signing algorithm. If the endpoint returns 404 or 410, Flick falls back to DNS TXT records, checking the domain and then its parents as described in Key discovery.
The root key grants publishing permission through a signed Biscuit, using the delegation model described in the Macaroons paper. When you publish with flkr, it reads the root private key from 1Password and creates a one-hour Biscuit authorizing a delegate key to publish that view. It can reuse an existing local identity key; otherwise it generates a temporary key pair.
flkr includes the Biscuit with the publication and signs the request with the delegate private key. Flick uses the discovered root public key to verify the Biscuit, then checks the publisher, view, and expiration before accepting the update.
1. Create the root key pair
A key pair is generated automatically in your browser. Save it below. Enter the trust-root URL and app bundle ID to prepare the public-key document for the next step.
Save as Flick SDK Root Authority in your Private vault. Requires the 1Password browser extension.
2. Publish the public key
The JSON above binds your trust-root URL (iss) and bundle ID (sub) to the public key (kid). Use it unchanged after entering your own URL and bundle ID. The URL must match the Swift declaration exactly, including any path or trailing slash.
You can publish this document via HTTPS or DNS.
2.1. Publish via HTTPS
For HTTPS discovery, serve the JSON at https://acme-corp.com/.well-known/flick with a JSON content type and no redirect. Check the response with:
curl --fail https://acme-corp.com/.well-known/flickCompare the response with the generated JSON: iss must equal the Swift scope URL, sub must equal your app’s bundle ID, and kid must contain the generated public key.
2.2. Publish via DNS
For DNS discovery, encode the JSON array as UTF-8, then base64url without padding (no trailing = characters). Publish that encoded value as a TXT record at _flick.acme-corp.com. Copy DNS TXT value above performs this encoding for you.
Generate the DNS TXT value with Node.js
This example uses your generated public key, trust-root URL, and bundle ID:
Generating your public key…
Paste the output into your DNS TXT record. Node’s built-in base64url encoding omits padding automatically.
The well-known endpoint must return 404 or 410 for Flick to reach DNS. See Key discovery for the complete lookup order and stopping conditions.
3. Publish a Flick
Install flkr:
npm install -g @flickfyi/flkrThe app’s Swift declarations, generated contract, and Flick implementations live together:
MyApp/
├── MyApp.xcodeproj
├── MyApp/
│ └── FlickScopes.swift
├── generated/
│ ├── package.json
│ └── src/
│ ├── index.js
│ ├── index.d.ts
│ └── …
└── flicks/
├── package.json
├── tsconfig.json
└── src/
└── card.tsxFrom your app’s Xcode project directory, publish all views:
flkr publishflkr refreshes generated/ from your app’s Swift declarations using the Swift SDK’s generator. This declares a card view with no input properties:
import FlickSDK
@FlickScope(url: "https://acme-corp.com")
enum App {
enum Views {
struct Card: Codable, Sendable {}
}
}flkr looks for .ts and .tsx implementations under flicks/src/. flkr creates flicks/package.json and flicks/tsconfig.json with the SDK dependencies, Photon JSX settings, and strict type checking.
The TypeScript file default-exports a defineView binding to the matching generated descriptor:
import { defineView, Text } from '@flickfyi/core'
import { App } from 'com.acme.myapp'
export default defineView(App.views.card, function Card() {
return <Text>Hello from Flick</Text>
})Here, com.acme.myapp is the package name in generated/package.json, and App.views.card comes from the Swift App.Views.Card declaration. flkr publish publishes every declared view’s implementation. Each declared view must have an implementation before publishing.
Set up CI credentials
To publish from CI, create a delegate credential for the job. Run this from the app’s Xcode project directory:
flkr delegate --output flick.credentials.jsonThis reads the root key once and writes a new delegate key pair and its Biscuit to the JSON file. The file also records the trust root, root public key, algorithm, expiration, and format version. The grant covers the app’s declared views and expires after 365 days. Use --expires-in-days 30 to choose a different lifetime.
Store the file as a CI secret. It contains a private key; keep it out of source control. Make the app’s generated/ contract and flicks/ sources available to the job, install the flicks/ dependencies, then pass the credential path when publishing:
flkr publish --host ./generated --credential ./flick.credentials.json --view 'apple:bundle-id:com.acme.myapp/app#views/card'--host uses the existing generated contract. Use a view URI from that contract. flkr signs with the file’s private key and includes its Biscuit. A supplied credential takes precedence over the root-key configuration. Create a replacement credential before its delegation expires.
Key discovery
For https://preview.acme-corp.com/team, Flick checks these locations in order:
| Order | Location | When Flick continues |
|---|---|---|
| 1 | https://preview.acme-corp.com/.well-known/flick | Only after HTTP 404 or 410 |
| 2 | TXT _flick.preview.acme-corp.com | Only when no records exist |
| 3 | TXT _flick.acme-corp.com | End of search |
The HTTPS lookup always uses the origin’s /.well-known/flick, even when the issuer has a path. Every binding must retain the full issuer—in this example, https://preview.acme-corp.com/team—and the exact app bundle ID.
DNS walks one parent at a time and stops at the registrable domain, accounting for public and private suffixes. It never checks _flick.com. A record on a parent must explicitly include the subdomain’s issuer; it is not a wildcard grant.
The first available document or record set is authoritative. Invalid data, a missing matching binding, or a rejected delegation stops verification rather than continuing to another source. Network failures, TLS errors, redirects, and DNS lookup errors also stop discovery. DNS without a responding HTTPS endpoint is not supported by the current implementation.
For a TXT record split into quoted chunks, Flick joins the chunks before decoding. Separate TXT records are decoded separately. Both discovery methods accept arrays with multiple apps or rotation keys.
Configuration
| Setting | Use |
|---|---|
FLICK_ROOT_PRIVATE_KEY_REF | Read the root key from another 1Password vault or item |
FLICK_ROOT_PRIVATE_KEY | Supply the Base58 root private key directly; overrides 1Password |
FLICK_PUBLISHING_CREDENTIAL | Path to the delegate JSON file; --credential overrides it |
export FLICK_ROOT_PRIVATE_KEY_REF='op://Engineering/Flick SDK Root Authority/password'Rotate a root key
Save a new key pair, add its public binding to your discovery document or DNS record, and save the new private key in the 1Password item used by flkr. Remove the old binding when you no longer want to accept publications authorized by it. Previously accepted updates remain available.
Troubleshooting
| Problem | Check |
|---|---|
| Cannot read the root key | 1Password CLI is set up and unlocked; the item is in the vault named by the secret reference |
No generated SDK project found | Run flkr from the app’s Xcode project directory; use --host to publish from an existing generated contract |
Create this view implementation before publishing | Select the view in flkr and press Enter to create its source file |
| Delegation rejected | The publisher key matches the grant, the view is included, and the grant has not expired |
No discovered root authorizes this publication | Compare the discovery binding’s iss and sub with the scope URL and bundle ID, then check the publisher key, view URI, and expiration in the delegation |
Trust-root HTTPS discovery failed | Check the endpoint response: only 404 or 410 allows DNS fallback; connection errors, TLS failures, redirects, and other error statuses stop discovery |
| No update appears | The Swift scope and publication use the same trust-root URL |