Identify the current credential
- CLI
- salesdogg account whoami
- MCP tool
- account_whoami
- Credentials
- Session, Secret key
Returns the user (session) or the project (API key) the credential belongs to. Useful to verify setup.
SalesDogg API 0.1.0. Generated from /openapi.json, the same document the CLI and the MCP server are generated from.
SalesDogg is a subscription and in-app purchase backend. It normalizes Stripe, Braintree, App Store and Google Play into one model: customers own subscriptions and purchases of products; products unlock entitlements; offerings group products for display.
Send Authorization: Bearer <credential>.
sd_sk_...): full access to one project. Server-side only.sd_pk_...): safe in client apps; read offerings, check a user's entitlements, submit App Store / Google Play receipts, attach an anonymous user to a signed-in one, set customer attributes. It never returns attributes or purchase history.salesdogg login): a human user. Project-scoped endpoints then need an X-Project-Id header. Some operations need the owner or admin role in the organization; each endpoint says so.POST /v1/simulator/purchases with { "app_user_id": "user_1", "store_identifier": "pro_monthly" } creates a sandbox
subscription with no setup. Attach its product to an entitlement, check the user's access, then drive renewals, cancellations,
billing issues and refunds with POST /v1/simulator/subscriptions/{id}/advance. Events and webhooks behave as in production.
POST /v1/apps – connect a provider.POST /v1/apps/{app_id}/import_products (or POST /v1/products).POST /v1/entitlements with product_ids.POST /v1/checkout_sessions (web) or POST /v1/receipts (mobile) to record purchases.GET /v1/customers/{app_user_id}/entitlements to check access.POST /v1/imports/revenuecat/catalog, then POST /v1/imports/revenuecat/customers page by page (or salesdogg import revenuecat).GET /v1/subscribers/{app_user_id} and the other /v1/subscribers endpoints answer in RevenueCat's REST v1 format.format: "revenuecat" receive RevenueCat's webhook body.$RCAnonymousID: ids and restore behavior (GET/PATCH /v1/settings) follow RevenueCat's rules.{ "error": { type, code, message, param?, hint? } }. hint says what to do next.lookup_key, app_user_id or store_identifier are idempotent upserts and safe to retry.limit + starting_after; responses carry has_more and next_cursor.RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds). A 429 includes Retry-After.Returns the user (session) or the project (API key) the credential belongs to. Useful to verify setup.
A project holds all billing data for one product. If you have no organization yet, one is created. The response includes a secret and a public API key so you can start immediately.
| Parameter | In | Type | Description |
|---|---|---|---|
| name* | body | string | |
| organization_id | body | string | Defaults to your only organization, or creates one named after the project. |
Needs the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| project_id* | path | string |
Secret keys (sd_sk_) have full access to the project and must stay on servers. Public keys (sd_pk_) are safe to ship in client apps: they can only read offerings, check a user's entitlements, submit App Store and Google Play receipts, attach an anonymous user to a signed-in one, and set (not read) customer attributes. Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| kind* | body | "secret" | "public" | |
| name | body | string |
Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| api_key_id* | path | string |
Creates a connection ("app") to one provider account or store listing. Required fields per provider: - stripe: credentials { secret_key, webhook_secret? }. When the API has a public HTTPS URL the webhook endpoint is registered in Stripe automatically. - braintree: credentials { merchant_id, public_key, private_key, environment }, config { customer_id_is_app_user_id? }. - app_store: config { bundle_id, app_apple_id? }, credentials { issuer_id, key_id, private_key } (optional: an In-App Purchase key, needed to look purchases up at Apple, e.g. for the RevenueCat import). - play_store: credentials { service_account_json }, config { package_name }. The response contains `webhook_url` and `webhook_instructions` describing the one remaining manual step, if any. The Free plan is sandbox only. Live credentials (a live Stripe key, Braintree `environment: production`, an App Store `app_apple_id`) on Free answer 402 `plan_required`; show the user `error.details.upgrade_url`, a link to choose a plan and pay, then retry. Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| provider* | body | "stripe" | "braintree" | "app_store" | "play_store" | "simulator" | |
| name | body | string | |
| credentials | body | object | Provider secrets. Stored encrypted; never returned. |
| config | body | object | Non-secret provider settings. |
| Parameter | In | Type | Description |
|---|---|---|---|
| app_id* | path | string |
Given credential and config fields are merged into the stored ones (e.g. set a Stripe `webhook_secret` after creating the endpoint). Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_id* | path | string | |
| name | body | string | |
| credentials | body | object | |
| config | body | object |
Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_id* | path | string |
Fetches the provider's catalog (Stripe prices, Braintree plans) and creates any products that do not exist yet. Idempotent.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_id* | path | string |
| Parameter | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Page size (1-100). |
| starting_after | query | string | Cursor: the `next_cursor` value from the previous page. |
| app_id | query | string |
Registers a store product so it can be attached to entitlements and offerings. Idempotent on (app_id, store_identifier): creating an existing product updates and returns it.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_id* | body | string | |
| store_identifier* | body | string | |
| type | body | "subscription" | "one_time" | |
| display_name | body | string |
| Parameter | In | Type | Description |
|---|---|---|---|
| product_id* | path | string |
An entitlement is a level of access (e.g. "pro") unlocked by any of its products. Idempotent on lookup_key. When product_ids is given it replaces the attached products.
| Parameter | In | Type | Description |
|---|---|---|---|
| lookup_key* | body | string | |
| display_name | body | string | |
| product_ids | body | string[] |
| Parameter | In | Type | Description |
|---|---|---|---|
| entitlement* | path | string | Entitlement id or lookup_key. |
| product_ids* | body | string[] |
| Parameter | In | Type | Description |
|---|---|---|---|
| entitlement* | path | string | Entitlement id or lookup_key. |
| product_id* | path | string |
| Parameter | In | Type | Description |
|---|---|---|---|
| entitlement* | path | string | Entitlement id or lookup_key. |
Offerings are the sets of products you present to customers. Client apps fetch this with a public key and display the one with `is_current: true`.
Idempotent on lookup_key. When `packages` is given it replaces the offering's packages. The first offering of a project becomes current automatically.
| Parameter | In | Type | Description |
|---|---|---|---|
| lookup_key* | body | string | |
| display_name | body | string | |
| is_current | body | boolean | |
| metadata | body | object | |
| packages | body | object[] |
| Parameter | In | Type | Description |
|---|---|---|---|
| offering* | path | string | Offering id or lookup_key. |
| Parameter | In | Type | Description |
|---|---|---|---|
| offering* | path | string | Offering id or lookup_key. |
Newest first. Filters combine with AND. Each customer comes with attributes, aliases and last-seen device details.
| Parameter | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Page size (1-100). |
| starting_after | query | string | Cursor: the `next_cursor` value from the previous page. |
| search | query | string | An app user id or alias (exact, or the start of one), or an exact $email address (case-insensitive). |
| entitlement | query | string | Only customers with this entitlement (lookup_key) active right now. |
| active | query | "true" | "false" | true: customers with any active entitlement; false: customers with none. |
| platform | query | string | Last seen platform, e.g. ios or android. |
| country | query | string | Last seen country, ISO 3166-1 alpha-2. |
| app_version | query | string | Last seen app version. |
| store | query | "stripe" | "braintree" | "app_store" | "play_store" | "simulator" | Customers with a subscription or purchase from this store. |
| attribute_key | query | string | With attribute_value: customers whose attribute equals the value (case-insensitive). Alone: customers that have the attribute. |
| attribute_value | query | string | |
| first_seen_after | query | string | |
| first_seen_before | query | string | |
| last_seen_after | query | string | |
| last_seen_before | query | string |
Idempotent on app_user_id (an alias finds its customer). Given attributes are merged into existing ones; set a key to null to remove it.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | body | string | |
| attributes | body | object | Key -> value. A value may also be `{ value, updated_at_ms }` (RevenueCat format). null or "" deletes the attribute. |
Same rules as RevenueCat: string values up to 500 characters; null or "" deletes; custom keys start with a letter, use letters, digits, "_" and "-", up to 40 characters, at most 50 per customer; keys starting with "$" are reserved ($email, $displayName, $phoneNumber, $mediaSource, $campaign, $adGroup, $ad, $keyword, $creative, integration ids). Send `{ value, updated_at_ms }` to say when the change was made; an older write never replaces a newer one. Client apps may call this with the public key (creating the customer if needed), but cannot read attributes back: the response then carries entitlements only. From an app, campaign attribution keys can only be set once.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | Your own identifier for the user (URL-encoded). An alias works too. |
| attributes* | body | object | Key -> value. A value may also be `{ value, updated_at_ms }` (RevenueCat format). null or "" deletes the attribute. |
Client SDKs start with an anonymous id (`$anon:<uuid>`; RevenueCat's `$RCAnonymousID:<32 hex>` is accepted too). When the person signs in, call this to move everything the anonymous customer bought, and its attributes, to `app_user_id`. The anonymous id keeps working as an alias. Safe to repeat; if the anonymous customer does not exist it only makes sure the user exists. With a public key the response contains the user's entitlements only.
| Parameter | In | Type | Description |
|---|---|---|---|
| anonymous_id* | body | string | |
| app_user_id* | body | string |
Server-side only. Client apps check access with GET /v1/customers/{app_user_id}/entitlements. An alias finds its customer.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | Your own identifier for the user (URL-encoded). An alias works too. |
Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | Your own identifier for the user (URL-encoded). An alias works too. |
The access check. Returns the entitlements that are active right now. Unknown users return an empty list rather than an error, so it is safe to call for any user. Calls from an app (public key, or any key with an X-Platform header) update the customer's last-seen details.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | Your own identifier for the user (URL-encoded). An alias works too. |
Gives the user an entitlement directly (promotions, support, testing). Creates the customer if needed.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | Your own identifier for the user (URL-encoded). An alias works too. |
| entitlement* | body | string | Entitlement lookup_key or id. |
| expires_at | body | string | Omit for a grant that never expires. |
Revokes all active grants of the entitlement for this user. Access from purchases is unaffected.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | Your own identifier for the user (URL-encoded). An alias works too. |
| entitlement* | path | string | Entitlement lookup_key or id. |
| Parameter | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Page size (1-100). |
| starting_after | query | string | Cursor: the `next_cursor` value from the previous page. |
| app_user_id | query | string | |
| status | query | "trialing" | "active" | "in_grace_period" | "past_due" | "paused" | "expired" | |
| environment | query | "production" | "sandbox" |
| Parameter | In | Type | Description |
|---|---|---|---|
| subscription_id* | path | string |
| Parameter | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Page size (1-100). |
| starting_after | query | string | Cursor: the `next_cursor` value from the previous page. |
| app_user_id | query | string |
Validates the purchase with the store, records it for the user and returns their updated customer record. Call it right after a purchase or restore. `token` per provider: - app_store: the StoreKit 2 transaction `jwsRepresentation`. - play_store: the purchase token (also pass `store_product_identifier` for one-time products). - stripe / braintree: the subscription id (links an existing subscription to the user). Secret key only: unlike a store receipt, the id does not prove who bought it. If the purchase already belongs to another identified user, the project's restore behavior decides (GET/PATCH /v1/settings): `transfer` moves it to this user (a `transfer` event is recorded); `keep_with_original` refuses with 409 `receipt_already_in_use`. Purchases of anonymous users always move. With a public key the response contains the user's entitlements only: `attributes`, `subscriptions` and `purchases` are empty.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | body | string | |
| app_id | body | string | The app (provider connection) the purchase was made through. |
| provider | body | "stripe" | "braintree" | "app_store" | "play_store" | "simulator" | Alternative to app_id when the project has exactly one app for this provider. Client SDKs send this. |
| token* | body | string | |
| store_product_identifier | body | string |
Returns a URL to send the user to. The resulting purchase is attributed to `app_user_id` automatically. Currently supported for Stripe products.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | body | string | |
| product_id* | body | string | Product to sell (a Stripe product). |
| success_url* | body | string | |
| cancel_url | body | string | |
| customer_email | body | string | |
| trial_days | body | integer |
Every change to a subscription or purchase is recorded as an event, regardless of which store it came from. The same payloads are sent to webhook endpoints.
| Parameter | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Page size (1-100). |
| starting_after | query | string | Cursor: the `next_cursor` value from the previous page. |
| app_user_id | query | string | |
| type | query | "initial_purchase" | "trial_started" | "trial_converted" | "renewal" | "cancellation" | "uncancellation" | "billing_issue" | "billing_recovered" | "paused" | "resumed" | "product_change" | "expiration" | "refund" | "non_renewing_purchase" | "transfer" | "grant_created" | "grant_revoked" | "test" |
Events are POSTed as JSON and retried with exponential backoff (8 attempts over about 2 days) until your endpoint returns 2xx. The signing secret is only returned here. Moving from RevenueCat: set `format: "revenuecat"` to receive RevenueCat's webhook body, and `authorization_header` to the value your receiver already checks. Events RevenueCat has no counterpart for (billing_recovered, which RevenueCat reports as the RENEWAL that follows, and grants) are not sent to such endpoints. Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| url* | body | string | |
| event_types | body | "initial_purchase" | "trial_started" | "trial_converted" | "renewal" | "cancellation" | "uncancellation" | "billing_issue" | "billing_recovered" | "paused" | "resumed" | "product_change" | "expiration" | "refund" | "non_renewing_purchase" | "transfer" | "grant_created" | "grant_revoked" | "test"[] | Omit to receive all events. |
| format | body | "salesdogg" | "revenuecat" | |
| authorization_header | body | string | Sent verbatim as the Authorization header of every delivery, as RevenueCat does. |
Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| endpoint_id* | path | string |
Emits an event of type "test". Check the outcome with GET /v1/webhook_deliveries.
| Parameter | In | Type | Description |
|---|---|---|---|
| endpoint_id* | path | string |
| Parameter | In | Type | Description |
|---|---|---|---|
| limit | query | integer | Page size (1-100). |
| starting_after | query | string | Cursor: the `next_cursor` value from the previous page. |
| endpoint_id | query | string | |
| status | query | "pending" | "succeeded" | "failed" |
| Parameter | In | Type | Description |
|---|---|---|---|
| environment | query | "production" | "sandbox" |
One point per UTC day: active subscriptions, trials and MRR at the end of the day, plus the day's new subscriptions, trial conversions, churn, cancellations, refunds and revenue. At most 366 days per request. MRR is based on each subscription's current price.
| Parameter | In | Type | Description |
|---|---|---|---|
| from | query | string | First day (YYYY-MM-DD, UTC). Defaults to 29 days before `to`. |
| to | query | string | Last day (YYYY-MM-DD, UTC). Defaults to today. |
| environment | query | "production" | "sandbox" |
Creates a purchase without any store, so you can build and test against SalesDogg before connecting a provider. It goes through the same path as a real purchase: entitlements update, events are recorded and your webhook endpoints are called. Simulated data is always in the sandbox environment. Name the product either way: - `store_identifier`: any name you like (e.g. "pro_monthly"). The simulator app and the product are created if they do not exist. Zero setup. - `product_id`: an existing product, including one imported from a real provider, to test your real catalog and entitlements without paying. Subscriptions start active, or trialing when `trial_days` is set. Move them on with POST /v1/simulator/subscriptions/{subscription_id}/advance.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | body | string | |
| store_identifier | body | string | |
| product_id | body | string | |
| type | body | "subscription" | "one_time" | Only with store_identifier, when the product does not exist yet. Default subscription. |
| trial_days | body | integer | Start a subscription with a free trial of this length. |
| billing_period | body | string | ISO 8601 duration of one billing cycle, e.g. P1M, P1Y, P1W. |
| price_amount | body | integer | Price per billing period in minor units (cents). |
| currency | body | string |
Applies one change to a subscription created by the simulator and returns the events it produced: - `renew`: start the next billing period (converts a trial, recovers a billing issue, resubscribes an expired subscription). - `cancel` / `uncancel`: turn auto-renew off or back on. Access continues until `expire`. - `fail_payment` / `recover`: enter and leave the billing-issue grace period. - `pause` / `resume`. - `expire`: end the subscription now; access is removed. - `refund`: refund the last charge and end the subscription. Only simulated subscriptions can be changed this way; real ones are driven by their store.
| Parameter | In | Type | Description |
|---|---|---|---|
| subscription_id* | path | string | |
| action* | body | "renew" | "cancel" | "uncancel" | "fail_payment" | "recover" | "pause" | "resume" | "expire" | "refund" |
Marks the purchase refunded, which removes the access it granted.
| Parameter | In | Type | Description |
|---|---|---|---|
| purchase_id* | path | string |
Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| restore_behavior | body | "transfer" | "keep_with_original" |
RevenueCat REST API v1 compatible: same path, request and response shape. Creates the customer if it does not exist, as RevenueCat does. Send X-Platform only when the request is on behalf of the app; it updates last seen. grace_period_expires_date is always null; GET /v1/customers/{app_user_id}/entitlements is the authoritative access check.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | The app user id (or any alias). |
RevenueCat REST API v1 compatible: same path, request and response shape. Deletes the customer, their aliases, attributes and purchase history. Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | The app user id (or any alias). |
RevenueCat REST API v1 compatible: same path, request and response shape. Body: { "attributes": { "<key>": { "value": "...", "updated_at_ms": 1709195668093 } } }. A null or empty value deletes the attribute; an older updated_at_ms never replaces a newer value.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | The app user id (or any alias). |
| attributes* | body | object |
RevenueCat REST API v1 compatible: same path, request and response shape. Pass duration (daily, three_day, weekly, two_week, monthly, two_month, three_month, six_month, yearly, lifetime) or end_time_ms. start_time_ms only moves the end of a duration; the grant is active from now. Same as POST /v1/customers/{app_user_id}/grants.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | The app user id (or any alias). |
| entitlement_identifier* | path | string | Entitlement lookup_key. |
| duration | body | "daily" | "three_day" | "weekly" | "two_week" | "monthly" | "two_month" | "three_month" | "six_month" | "yearly" | "lifetime" | |
| start_time_ms | body | integer | |
| end_time_ms | body | integer |
RevenueCat REST API v1 compatible: same path, request and response shape. Revokes every active grant of the entitlement. Access from purchases is unaffected.
| Parameter | In | Type | Description |
|---|---|---|---|
| app_user_id* | path | string | The app user id (or any alias). |
| entitlement_identifier* | path | string | Entitlement lookup_key. |
Step 1 of moving from RevenueCat. Connect your stores first (POST /v1/apps); RevenueCat apps are matched to them by bundle id / package name, or by store when there is one app per store. Products are created under the matching app (a Stripe product becomes one product per active price), entitlements and offerings by lookup_key. Safe to run again. Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| api_key* | body | string | A RevenueCat REST API v2 secret key with read access (project configuration and customer information). Used for this request only; never stored. |
| project_id* | body | string | The RevenueCat project id (proj...). |
| app_map | body | object | RevenueCat app id -> SalesDogg app id, only needed when a store has several SalesDogg apps that cannot be told apart by bundle id / package name. |
Step 2, after the catalog. Call repeatedly with `starting_after: next_cursor` until `has_more` is false (`salesdogg import revenuecat` does this for you). Safe to re-run any page. Brings over app user ids and aliases, attributes, first/last seen and device details, and promotional entitlements (as grants). Store purchases are looked up at the store and recorded exactly as a store notification would be: Stripe through its API, Google Play through the Orders API, the App Store when the app has App Store Server API credentials. The rest count as `awaiting_app_sync`: the SalesDogg SDK records them on the app's next launch. Expired subscriptions are skipped unless include_expired is true. Nothing imported sends webhooks. Point the stores' notifications at SalesDogg before importing so no change is missed in between. Signed-in users need the owner or admin role in the organization.
| Parameter | In | Type | Description |
|---|---|---|---|
| api_key* | body | string | A RevenueCat REST API v2 secret key with read access (project configuration and customer information). Used for this request only; never stored. |
| project_id* | body | string | The RevenueCat project id (proj...). |
| app_map | body | object | RevenueCat app id -> SalesDogg app id, only needed when a store has several SalesDogg apps that cannot be told apart by bundle id / package name. |
| starting_after | body | string | The next_cursor of the previous page. |
| limit | body | integer | Customers per call. Each takes about 4 RevenueCat requests plus one store lookup per purchase. |
| include_expired | body | boolean | Also look up subscriptions that have expired, for history. |
Free is sandbox only. On Free, `upgrade_url` is a link a person can open to choose a plan and pay without signing in; show it to the user when they want to go live. Connecting live credentials on Free answers 402 `plan_required` with the same link in `error.details.upgrade_url`.