Skip to content

Admin API

Prefix /v1/admin. Send Authorization: Bearer <token>, where the token is a session (kks_..., 12 hours) or a personal access token (kkp_...). App keys are not accepted. Bodies and responses are JSON with the usual error format. Paths below use :t for the tenant id and :a for the app id.

A user who is not a member of the tenant gets 404 tenant_not_found; a member without the permission gets 403 forbidden. Permissions are listed in Keys and access. The CLI covers the app endpoints.

Method Path Notes
POST /auth/login {email, password, totp_code} → {token, expires_at, mfa, user}. Errors: invalid_credentials, totp_required, totp_invalid, totp_reused, totp_clock_skew
POST /auth/logout Ends the current session
POST /invitations/accept {token, password, name}. A new user gets a session that can only enroll TOTP
GET /me Current user and memberships
POST /me/totp/setup Returns secret and otpauth_uri
POST /me/totp/enable {code}; upgrades the session
DELETE /me {password, totp_code}. Deletes the account; an owner of a tenant with other members must transfer ownership first (409 owner_must_transfer)
GET, POST /me/tokens List or create personal access tokens. POST {name, expires_in_days?} (1 to 3650), only from a session; the token is shown once
DELETE /me/tokens/:token_id Revoke a token

Until TOTP is enabled every other call returns 403 totp_enrollment_required.

Method Path Permission Notes
GET /tenants Any Tenants you belong to
GET /tenants/:t tenant.read
PATCH /tenants/:t tenant.update {name}
DELETE /tenants/:t tenant.delete Removes business data, keeps the audit log
GET /tenants/:t/members member.read
PATCH /tenants/:t/members/:user_id member.manage {role}: admin, developer or readonly. The owner cannot be changed
DELETE /tenants/:t/members/:user_id member.manage Any member can remove themselves, except the owner
GET, POST /tenants/:t/invitations member.manage POST {email, role} returns a token valid for 7 days, shown once
DELETE /tenants/:t/invitations/:invitation_id member.manage Revoke
POST /tenants/:t/transfer-ownership owner.transfer {user_id}; the new owner must have TOTP enabled. The old owner becomes admin
GET /tenants/:t/audit audit.read ?limit=1..500&before=<id>, newest first, returns next_before
Method Path Permission Notes
GET /tenants/:t/apps app.read Optional exact filters ios_bundle_id, android_package
POST /tenants/:t/apps app.write App fields below, plus optional apple and google credentials. Returns secret_key and webhook_secret once
GET /tenants/:t/apps/:a app.read Includes the inbound callback paths
PATCH /tenants/:t/apps/:a app.write Only the fields you send change; null clears
DELETE /tenants/:t/apps/:a app.delete Deletes the app and its data
POST /tenants/:t/apps/:a/keys/rotate keys.rotate {webhook_secret: true} also replaces the webhook secret. Old keys stop working at once
POST /tenants/:t/apps/:a/webhooks/test webhooks.test See test deliveries

App fields:

Field Type
name string
ios_bundle_id, android_package string
apple_app_apple_id integer
apple_environment production, sandbox
apple_refund_preference DECLINE, GRANT_FULL, GRANT_PRORATED
google_pubsub_audience, google_pubsub_email string
google_consumable_products string[]; a trailing * is a prefix
webhook_url string
checkout_return_hosts string[], up to 50
cors_origins string[], up to 50
ads_unmapped_policy reject, emit
ads_allow_shared_test_units boolean

Credentials are write-only. Reads return summaries and fingerprints (the first 8 hex digits of a SHA-256), never values.

Method Path Permission Notes
GET /tenants/:t/apps/:a/credentials app.read Summary per channel
PUT, DELETE /tenants/:t/apps/:a/credentials/apple credentials.write {issuer_id, key_id, private_key, app_apple_id?, environment?}
PUT, DELETE /tenants/:t/apps/:a/credentials/google credentials.write {service_account_json, pubsub_audience?, pubsub_email?}
PUT, DELETE /tenants/:t/apps/:a/credentials/:channel credentials.write Other channels and ad networks: {values: {key: value or null}}. Keys: stripe secret_key, webhook_secret; ccbill client_accnum, client_subacc, salt, datalink_username, datalink_password, webhook_ips, environment; applovin event_key; admob app_id; levelplay private_key, app_key, timestamp_window_seconds, ip_allowlist
Method Path Permission Notes
GET /channels Any Channel plugins and their capabilities
GET /tenants/:t/apps/:a/channels app.read Plus the credential check for this app
GET /tenants/:t/apps/:a/channels/:channel/products app.read Product mapping
PUT /tenants/:t/apps/:a/channels/:channel/products/:product_id app.write {external_id, type}, type is subscription or one_time
DELETE /tenants/:t/apps/:a/channels/:channel/products/:product_id app.write
POST /tenants/:t/apps/:a/channels/:channel/refunds refunds.write Channels with refund: true (CCBill): {transaction_id, amount_micros?}. Other channels return 404 refund_not_supported
POST /tenants/:t/apps/:a/channels/:channel/simulate app.write Channels with simulate: true (CCBill): replay a channel notification in the test environment; needs the test store on

Capabilities reported per channel: subscriptions, one_time_purchases, refunds, refund_lookup, active_refund, reconcile, sandbox, chargebacks.

Method Path Permission Notes
GET /tenants/:t/apps/:a/test-store app.read Settings and warnings
PUT /tenants/:t/apps/:a/test-store app.write {enabled?, subscription_period_seconds?, renewal_success_rate?, grace_period_seconds?, billing_retry_seconds?, products?, prices?}
GET /tenants/:t/apps/:a/test-store/purchases app.read ?app_user_id=&limit=1..500
POST /tenants/:t/apps/:a/test-store/simulate app.write {scenario, purchase_id?, app_user_id?, product_id?, to_product_id?}
POST /tenants/:t/apps/:a/test-store/advance app.write {app_user_id, purchase_id?, outcome?}, outcome is success or failure
Method Path Permission Notes
GET /ad-networks Any Ad network plugins
GET /tenants/:t/apps/:a/ad-networks app.read Callback paths and credential checks for this app
GET /tenants/:t/apps/:a/ad-units app.read {ad_units: [{name, networks, units, updated_at}]}
PUT /tenants/:t/apps/:a/ad-units/:name app.write {networks: {admob?, applovin?, levelplay?}}; see Ad unit mapping
DELETE /tenants/:t/apps/:a/ad-units/:name app.write
GET /tenants/:t/apps/:a/ads/unmatched app.read ?limit=1..500
GET, PUT /tenants/:t/apps/:a/test-ads app.read / app.write PUT {enabled}
POST /tenants/:t/apps/:a/ads/simulate app.write {app_user_id, ad_unit, reward_type?, reward_amount?, custom_data?, outcome?, network_transaction_id?, network?}

Every write is recorded in the audit log.