Skip to main content
The iOS SDK talks to two endpoints: resolve (fetch the flow for a placement and user, with a batch variant that warms several placements in one round-trip) and events (send analytics). Most developers never call these directly. This page is here for debugging and advanced use. Treat the SDK as the supported surface; this contract is owned by the backend and can change.

Base URLs

EnvironmentBase URL
productionhttps://api.flowpilot.io/v1
staginghttps://staging-api.flowpilot.io/v1
developmenthttps://dev-api.flowpilot.io/v1

Authentication

Every request carries an SDK API key as a bearer token:
Authorization: Bearer fp_live_xxxxxxxx_...
Keys are workspace-scoped and must be type sdk. A key of any other type is rejected with 401. Key prefixes:
  • fp_live_ for production keys.
  • fp_test_ for staging/test keys.
Create and manage keys in the dashboard (see API keys). The workspace is derived from the key; you never send a workspace ID.

Resolve

Fetch the flow for a placement and user.
POST /v1/apps/{appId}/placements/{placementId}/resolve
  • appId is the app UUID (the App ID from the dashboard).
  • placementId is the human-readable placement key (for example onboarding).

Request

user_id
string
required
Stable identifier for the user. The SDK uses a persistent per-install UUID by default.
session_id
string
required
Identifier for this session.
device_platform
string
required
One of ios, android, web.
attributes
object
Targeting attributes for audience matching (for example {"country": "US", "app.version": "2.1.0"}). See Audience targeting.

Response (200)

A match returns the flow. No match returns 200 with flow_id: null (and the backend records a resolve_no_flow event server-side).
flow_id
string | null
The matched flow ID, or null when no flow matched.
flow_version_id
string
The specific published version served.
flow_version
number
The version number.
schema_version
number
The wire format version (see Schema versions).
flow_schema
object
The full flow JSON. See Flow schema reference.
media_base_url
string
Base URL for relative image and media paths.
icon_base_url
string
Base URL for icon SVGs.
fonts
array
Font files to download and register. Each item is { family, weight, style, url }.
experiment_id
string
Set when an experiment is active on the placement.
variant_id
string
The assigned variant, when in an experiment.
variant_name
string
The assigned variant’s name.
cache_ttl_seconds
number
How long the SDK may treat the result as fresh. The SDK uses 300 (5 minutes) when omitted.

Status codes

CodeMeaning
200Resolved (including the no-flow case, flow_id: null).
400Missing/invalid app_id, placement_id, body, or validation failure.
401Missing/invalid key, or wrong key type.
403The app does not belong to the key’s workspace.
404App not found.
429Rate limited.

Variations

  • Offline export: add ?export=true to get the deterministic default flow for the placement. The resolver skips the audience filter and A/B assignment and always returns the placement’s default flow version, so the export is stable. Used to snapshot a flow for offline bundling.
  • Legacy route: POST /v1/placements/{placementId}/resolve exists for backward compatibility and is deprecated. Prefer the app-scoped route.

Example

curl -X POST \
  "https://api.flowpilot.io/v1/apps/APP_UUID/placements/onboarding/resolve" \
  -H "Authorization: Bearer fp_live_xxxxxxxx_..." \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "u_123",
    "session_id": "s_456",
    "device_platform": "ios",
    "attributes": { "country": "US", "app.version": "2.1.0" }
  }'

Batch resolve

Resolve several placements for one identity in a single round-trip. The SDK uses this for launch prefetch (when prefetchOnLaunch declares two or more placements) so it can warm many placements without one request per placement. If the call fails, the SDK falls back to per-placement resolves, so launch prefetch never depends on it.
POST /v1/apps/{appId}/placements/resolve-batch

Request

The identity fields (user_id, session_id, device_platform, attributes) apply to every key in placement_keys, exactly as a single resolve personalizes one placement.
placement_keys
string[]
required
The placement keys to resolve (for example ["onboarding", "paywall"]). Must be non-empty. Duplicates are removed server-side, and the de-duplicated set is capped at 50 placements.
user_id
string
required
Stable identifier for the user.
session_id
string
required
Identifier for this session.
device_platform
string
required
One of ios, android, web.
attributes
object
Targeting attributes for audience matching, applied to every placement. See Audience targeting.

Response (200)

A results array with one entry per de-duplicated placement key. Each entry is the same shape as a single resolve response (flow_id, flow_version_id, flow_schema, fonts, cache_ttl_seconds, and so on), tagged with its placement_key. Resolution is best-effort per placement, so one placement never sinks the batch:
  • A placement that matched no flow returns flow_id: null (and the backend records a resolve_no_flow event server-side, as with a single resolve).
  • A placement that failed to resolve returns flow_id: null and an error string. The overall response is still 200.
results
array
One result per requested placement key.
results[].placement_key
string
The placement key this result is for. Use it to correlate results regardless of order.
results[].error
string
Present only when that one placement failed to resolve. Absent on success and on the no-flow case.
Every other field on a result matches the single resolve response (flow_id, flow_version_id, flow_version, schema_version, flow_schema, media_base_url, icon_base_url, fonts, experiment_id, variant_id, variant_name, cache_ttl_seconds).

Status codes

CodeMeaning
200Resolved. Per-placement no-flow and per-placement errors are reported inside results, not as a non-200 status.
400Missing/invalid app_id, empty placement_keys, more than 50 placements, or a validation failure.
401Missing/invalid key, or wrong key type.
403The app does not belong to the key’s workspace.
404App not found.
429Rate limited.

Example

curl -X POST \
  "https://api.flowpilot.io/v1/apps/APP_UUID/placements/resolve-batch" \
  -H "Authorization: Bearer fp_live_xxxxxxxx_..." \
  -H "Content-Type: application/json" \
  -d '{
    "placement_keys": ["onboarding", "paywall"],
    "user_id": "u_123",
    "session_id": "s_456",
    "device_platform": "ios",
    "attributes": { "country": "US", "app.version": "2.1.0" }
  }'

Events

Send one analytics event or a batch.
POST /v1/apps/{appId}/events
The body is either a single event object or an array of them.

Request fields (EventInput)

Required:
event_id
string (uuid)
required
Unique event ID.
app_id
string (uuid)
required
Must match the appId in the URL (mismatches are rejected).
flow_id
string (uuid)
required
The flow the event belongs to.
flow_version_id
string (uuid)
required
Must reference a real flow version in the app, or the event is rejected.
user_id
string
required
Stable user identifier.
session_id
string
required
Session identifier.
device_platform
string
required
One of ios, android, web.
event_type
string
required
One of the recognized event types.
timestamp
string (RFC3339)
required
When the event happened.
sdk_version
string
required
The SDK version that produced the event.
Optional: placement_id, flow_version, experiment_id, variant_id, variant_name, screen_id, screen_name, screen_index, element_id, element_type, interaction_type, revenue, currency, time_since_flow_start_ms, time_on_screen_ms, app_version, country, properties.

Limits

  • Up to 100 events per request.
  • Up to 256 KB payload.

Response (202)

accepted
number
Events buffered for storage.
rejected
number
Events dropped (validation failure, app_id mismatch, or out-of-scope flow version).
The endpoint returns 202 Accepted; events are written to analytics asynchronously.

Variations

  • Legacy route: POST /v1/events exists for backward compatibility and is deprecated. Prefer the app-scoped route.

Example

curl -X POST \
  "https://api.flowpilot.io/v1/apps/APP_UUID/events" \
  -H "Authorization: Bearer fp_live_xxxxxxxx_..." \
  -H "Content-Type: application/json" \
  -d '[{
    "event_id": "550e8400-e29b-41d4-a716-446655440000",
    "app_id": "APP_UUID",
    "flow_id": "FLOW_UUID",
    "flow_version_id": "VERSION_UUID",
    "user_id": "u_123",
    "session_id": "s_456",
    "device_platform": "ios",
    "event_type": "flow_complete",
    "timestamp": "2026-01-10T12:00:05Z",
    "sdk_version": "1.0.0"
  }]'

Notes

  • The app-scoped routes are authoritative for app_id: the value in the URL wins, and a conflicting body app_id is rejected.
  • This contract may change. Build against the SDK, not raw HTTP, for production apps.