For developers

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.

Private beta · Base URL https://api.wandkit.app · OpenAPI 3.0.3 spec served at /api/openapi.yaml

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.

wk_

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.

Issued in project settings, or with wandkit apikeys create
wks_

Server 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.

Issued in project settings
wkp_

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.

Created in the dashboard

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.

  • Read scope 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.
  • Tokens always expire. The choices are 30, 90 or 365 days. There is no non-expiring option.
  • Revocation is itself token-allowed for write-scope tokens, on purpose, so cleaning up a leaked token can be scripted rather than clicked.
  • Optionally organization-scoped, 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.

forms

Feedback forms as code

Forms live in your repo as forms/.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.

responses

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.

events

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.

issues · campaigns

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              # writes .wandkit.yml

# forms as code: edit locally, review the diff, promote
wandkit forms pull                             # every form to forms/.json
wandkit forms diff forms/nps.json --project 
wandkit forms push forms/nps.json --project 

# responses out as NDJSON
wandkit responses export --form  --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//issues --paginate
wandkit api POST /projects//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.

Spec

/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.

Errors

error, code, message

Branch on code, a stable snake_case string. Never branch on message, which is localized and free to change.

Pagination

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.

  • No backend event ingest. Events come from the SDK in your app. There is no public endpoint for posting events from your servers.
  • No analytics endpoints today. 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.
  • No generic outbound webhooks. Notifications go to Slack and Discord, with 16 individually toggleable event types. If you need events pushed somewhere else, tell us.
  • No published rate limit and no SLA. 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.
  • No key environment split. 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.
  • Error messages ship in English and Croatian. 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.