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.