FAQ
The questions integrators actually ask, push first because that is where they start. Short answers here, links to the deeper pages where the detail lives. If an answer here and a schema in spec/ ever seem to disagree, the schema wins.
How do I turn on push notifications?
Declare surfaces.push.relay: true in your manifest. That one flag is the whole switch: without it there is no push, and with it the app is wired to the Parlane-operated hosted relay automatically. Then pair your server with the relay, ideally by putting a pre-minted pairing token in your connect QR so a single scan connects and pairs with zero extra user steps. If your server does not pair at connect, the app offers a manual pairing fallback in Settings. The full flow is in relay self-hosting.
Do I need Apple credentials to send push?
No. Never. Push to the Parlane app requires Apple's APNs key for the bundle id com.rubyn.client, and only Parlane holds that key. You send a push by calling POST /notify on the hosted relay with an encrypted payload; the relay does the Apple hop for you. If any tool or instruction asks you for a .p8 key, Key ID, or Apple Team ID to reach the Parlane app, it is wrong.
Can I self-host the relay and deliver push myself?
No. The open-source relay repo is for transparency and for self-hosting the transport, offline queueing, and encryption for custom clients. The current iOS app does not consume that tunnel or offline queue. The relay cannot deliver push to the Parlane app because it does not have, and cannot get, Parlane's Apple key for com.rubyn.client. If you need push, route it through the hosted relay. There is no self-hosted push path today; the honest limits are spelled out in relay self-hosting.
What is the relay URL? Do I set it?
https://relay.parlane.ai, the Parlane-operated hosted relay. Normally you do not set it: it is the default the moment you declare surfaces.push.relay: true, so surfaces.push.relayUrl stays omitted. It exists only for the rare host override, and your connect QR's relay param is populated with it automatically.
How do I test push? Mine never arrives.
Test against a TestFlight or App Store build, not a local development build. A push token's environment is baked into the build: TestFlight and App Store builds use production APNs, dev builds use sandbox. The hosted relay delivers to production APNs, so a push aimed at a dev build silently never shows up. Also read the /notify response: deviceCount: 0 means no device is registered and nothing was sent.
MCP or REST: which do I implement?
Either; both are first-class. MCP (streamable HTTP) is primary and supports streaming chat plus OAuth 2.1 PKCE login. REST is the roughly 50-line fallback: the manifest, POST /chat over Server-Sent Events, GET /ui/{id}, and POST /action, authenticated with a static bearer token. The document shapes are identical across both, so nothing is lost either way. Start with integrate, or go straight to the REST contract.
Do I need an SDK?
No. There is no SDK, and that is deliberate. You serve plain JSON that validates against the published schemas, from any language and any stack; the manifest reference documents every field. The fastest route is pasting the "make my app Parlane-ready" prompt from the quickstart into the coding agent you already use.
How does my server authenticate to the relay?
Through pairing, which mints two HMAC secrets: your server keeps serverAuthKey, the app keeps appAuthKey. Every relay call after pairing carries a v2 body-bound bearer signature (Authorization: Bearer v2.<ts>.<sig>, an HMAC over the timestamp and a SHA-256 hash of the exact request body). The hosted relay requires v2: legacy v1 tokens without the body hash are rejected. A serverId alone is public routing metadata and is never sufficient auth. Pairing itself is covered in relay self-hosting.
What does the Parlane app cost? What is Pro?
Free covers one connected app and every on-device feature: chat, voice, the full dashboard component catalog with view-only dashboards and local controls, themes, Siri Shortcuts, and the share sheet. Pro is $8.99/month or $79.99/year and adds unlimited connected apps, push notifications through the hosted relay, and two-way dashboards with interactive actions. Details and the reasoning behind the model are on the pricing page.
Building and debugging your integration
My dashboard shows "response was not valid JSON."
Your dashboard endpoint returned something that isn't a UI document, usually because source points at the wrong transport: an MCP-only backend declares "source": "/ui/home", the app GETs it, and a single-page-app or catch-all route answers with HTML. Match source to how you serve the document (parlane://ui/{id} for MCP, /ui/{id} for REST) and validate against ui-document.schema.json.
I tap a control and nothing happens. Can a badge be tappable?
Only a button or an interactive list row fires an action; a badge, text, or metric is display-only, and the schema rejects an action on them. For an approve/reject row, use two buttons. If a button does nothing, confirm a POST /action reaches your server, and note that Free users are routed to the paywall instead of calling your tool.
How do I make a destructive button red?
Set intent: "destructive". You don't pass a color; the catalog has no color field. destructive resolves to the negative semantic color, and a manifest theme.colors.negative changes what that color is within your app's screens. See the manifest reference.
My action succeeds, but the item it changed still shows.
A { ack: true } response changes nothing on screen. Return the full { ui } document reflecting the new state (full-replace), or set the action's refresh to re-fetch the dashboard. If the same action runs twice, deduplicate on action_id: the client can retry, so make the tool idempotent.
Sharing an image returns 413 or 502.
A 413 means the payload exceeded a body limit: base64 adds about 33% over the 10 MB attachment cap, so raise the tool endpoint's body limit past 20 MB and enforce the decoded cap yourself. A 502 means an upstream timed out or crashed mid-processing: acknowledge the share fast, then do OCR, indexing, or agent work asynchronously. See the REST contract.
My app doesn't appear in the iOS share sheet.
Declare every kind you accept in surfaces.share.accepts, including image and file if you ingest screenshots or documents. The app offers your target only for the kinds you list, and declaring file is also what makes the share extension eligible for documents at all.
I changed my manifest, but the app shows the old surfaces.
The app caches the manifest and refreshes it in the background. Foreground the app to pick up the change; the refresh is debounced to once per app every 5 minutes. To force it while testing, fully quit and relaunch the app. See the manifest reference.
My server voice played, then paused and came back robotic.
The client fell back to on-device text-to-speech after your /tts endpoint errored or timed out. A server-voice failure costs quality, never sound: any non-200, timeout, or network error finishes the reply with device speech and retries your server on the next reply. Keep per-sentence synthesis fast.
How do I tell whether a problem is Parlane or my backend?
Check the transport boundary. Call your endpoint directly (curl the manifest, a dashboard, or /action) and validate the response against the schemas in spec/. A valid document means the issue is client-side; malformed JSON, the wrong content type, or an error status means the issue is on your backend. The schemas are the contract both sides agree on.
Next
Question not here? The quickstart has the make-ready prompt, and llms-full.txt hands your coding agent the whole kit in one fetch.