API and CLI
The wandkit developer surface is three things: an iOS SDK inside your app, a command line tool for your repo and your CI, and one HTTP API that both the CLI and the dashboard speak. This page describes what is actually served today.
Three credentials, three jobs
wandkit issues three kinds of key and they are never interchangeable. A server key sent to an SDK endpoint is rejected, and an SDK key sent to a server endpoint is rejected. There is no live and test split. Keys are named, revocable, and the plaintext is shown once at creation and never again.
SDK key
Embedded in your iOS app and used by the SDK. Sent as X-API-Key. It is the only key that belongs in a shipped binary. Do not call SDK endpoints from your own backend with it.
wandkit apikeys createServer key
For your backend. Sent as X-API-Key. Today it authenticates exactly two endpoints, both listed further down this page. That is the whole server-to-server surface, and we would rather say so than imply more.
Personal access token
For you, the CLI, scripts and agents. Sent as Authorization: Bearer. Scoped read or write, expires in 30, 90 or 365 days, revocable at any time.
Personal access tokens
A token reaches the same API the dashboard uses, minus the operations that should stay tied to a signed-in session.
The spec classifies every operation that accepts a Bearer credential, and a test fails the build if a new endpoint arrives unclassified. Of the 147 such operations, 126 accept a personal access token and 21 are session-only. The session-only set is the account-critical work: creating further tokens, deleting your account, changing organization membership, and storing integration secrets for APNs, RevenueCat, App Store Connect and Google Play. A token cannot mint another token.
is enforced by method. A read token may issue GET, HEAD and OPTIONS, and gets a 403 with the code token_read_only on anything else.
The choices are 30, 90 or 365 days. There is no non-expiring option.
for write-scope tokens, on purpose, so cleaning up a leaked token can be scripted rather than clicked.
, restricting a token to a single organization you belong to.
# read the last 50 checkout_completed events for a project curl -H "Authorization: Bearer $WANDKIT_TOKEN" \ "https://api.wandkit.app/api/v1/projects/$PROJECT/events?event_name=checkout_completed&limit=50"
The CLI
wandkit from the command line, for humans, scripts and agents. Authenticates with a personal access token from WANDKIT_TOKEN or a stored login.
Feedback forms as code
Forms live in your repo as forms/<key>.json, one file per form. The key is the only identity, so files carry no project id or version and the same file can be pushed to staging and then to production. pull, push, diff, validate, list.
Response export
list for one page, export for all of them, auto-paginated, filtered with --since and --until. NDJSON by default, one object per line, so it pipes straight into whatever you already use. --format json if you want an array.
Integration check
list and tail. This is the tool for answering "is my SDK actually sending anything", and for discovering the event names you will trigger surveys on. It is a raw log today, not analytics.
Day to day work
Issues: list, create, update, close. Campaigns: list, get, create, update, delete, metrics. Plus projects, apikeys for SDK keys, auth and config.
# authenticate once, then bind this directory to a project wandkit auth login --with-token < token.txt wandkit projects use <staging-id> # writes .wandkit.yml # forms as code: edit locally, review the diff, promote wandkit forms pull # every form to forms/<key>.json wandkit forms diff forms/nps.json --project <prod-id> wandkit forms push forms/nps.json --project <prod-id> # responses out as NDJSON wandkit responses export --form <form-id> --since 30d > responses.ndjson # watch events land while you wire up the SDK wandkit events tail
The escape hatch
No workflow should be blocked on a missing wrapper command, so the CLI carries a raw authenticated request. Paths may omit the /api/v1 prefix. --paginate follows the standard envelope and merges every page; --input takes a file path or - for stdin.
wandkit api GET /me/tokens wandkit api GET /projects/<id>/issues --paginate wandkit api POST /projects/<id>/issues --input issue.json
.wandkit.yml is committed alongside your code and pins the project and forms directory, so CI jobs and agents need no flags. WANDKIT_TOKEN always takes precedence over a stored login, so a CI runner never needs a credentials file on disk. The CLI is not yet published for download. Write to info@wandkit.app and we will get you a build.
Server to server
Two endpoints take a wks_ server key. This is the complete list, not a selection.
Send a push to one user
POST /api/v1/push/send queues one notification for every device that user has registered. Delivery is asynchronous. Requires an APNs integration on the project. A repeated idempotency_key returns the original notification rather than queueing a second one, so retries are safe.
One caution we would rather state than bury: a device binds itself to a user id using the SDK key embedded in your app, so a determined client can bind to somebody else's id. Treat notification content as readable by anyone, and never put secrets or account data in it.
curl -X POST https://api.wandkit.app/api/v1/push/send \ -H "X-API-Key: wks_..." \ -H "Content-Type: application/json" \ -d '{"external_user_id":"user_123", "title":"Your report is live", "body":"Thanks for the bug report.", "idempotency_key":"report-live-8811"}'
Report a referral conversion
POST /api/v1/referrals/conversions. The app claims a referral first, then hands its install id to your backend, which reports the signup here. This call is deliberately yours to make: only your backend knows what a real signup is.
It always returns 200. A signup on an install that never claimed a referral is an ordinary organic signup, not an error, and comes back as converted: false with reason: no_claim. Reporting the same external_user_id twice is a no-op that returns the original outcome. When a conversion reaches the campaign's threshold, the inviter's reward is enqueued in the same transaction.
curl -X POST https://api.wandkit.app/api/v1/referrals/conversions \ -H "X-API-Key: wks_..." \ -H "Content-Type: application/json" \ -d '{"install_id":"...","external_user_id":"user_123"}'
Reference and conventions
The spec is the contract. These conventions hold across the API.
/api/openapi.yaml
The OpenAPI 3.0.3 document is served by the API itself, and every request under /api/v1 is validated against it at runtime, so the request contract cannot drift from the running server. Swagger UI is at /swagger.
error, code, message
Branch on code, a stable snake_case string. Never branch on message, which is localized and free to change.
items, total, limit, offset
One envelope across list endpoints. limit defaults to 20 and caps at 100. wandkit api --paginate walks it for you.
What this API does not do
Worth knowing before you plan around it. This is the surface as served today, not a statement about what is planned.
Events come from the SDK in your app. There is no public endpoint for posting events from your servers.
No aggregation, no funnels, no cohorts, no retention over HTTP. Behavioral event analytics is coming, and this section will change when it ships. Event data is a raw log plus a per-user occurrence counter, which is what drives the Nth-occurrence survey trigger.
Notifications go to Slack and Discord, with 16 individually toggleable event types. If you need events pushed somewhere else, tell us.
Magic-link sends and invitation resends have a cooldown window, but there is no general documented limit and no uptime commitment yet. wandkit is in private beta and we are not going to pretend otherwise.
There is no wk_live_ or wk_test_. Use separate projects for staging and production, which is also what makes forms-as-code promotion work.
That covers API error strings only. Server-driven localization is coming soon and is not part of the API surface yet. The code field is language-independent.
Something you need that is not here? Write to info@wandkit.app and tell us what you were trying to build. During beta, that is how the API gets decided.