# SalesDogg API reference

Version 0.1.0. Generated from https://salesdogg.com/openapi.json, the document the CLI and the MCP server are generated from. Each operation lists its CLI command and MCP tool name.

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.

## Authentication
Send `Authorization: Bearer <credential>`.
- **Secret key** (`sd_sk_...`): full access to one project. Server-side only.
- **Public key** (`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.
- **Session token** (from `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.

## Try it without a store account
`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.

## Quickstart
1. `POST /v1/apps` – connect a provider.
2. `POST /v1/apps/{app_id}/import_products` (or `POST /v1/products`).
3. `POST /v1/entitlements` with `product_ids`.
4. `POST /v1/checkout_sessions` (web) or `POST /v1/receipts` (mobile) to record purchases.
5. `GET /v1/customers/{app_user_id}/entitlements` to check access.

## Moving from RevenueCat
- `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.
- Webhook endpoints with `format: "revenuecat"` receive RevenueCat's webhook body.
- Customer attributes, aliases, `$RCAnonymousID:` ids and restore behavior (`GET/PATCH /v1/settings`) follow RevenueCat's rules.

## Conventions
- Errors: `{ "error": { type, code, message, param?, hint? } }`. `hint` says what to do next.
- Creates keyed by `lookup_key`, `app_user_id` or `store_identifier` are idempotent upserts and safe to retry.
- Lists: `limit` + `starting_after`; responses carry `has_more` and `next_cursor`.
- Money is in minor units (cents). Timestamps are ISO 8601 UTC.
- Rate limits: responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds). A 429 includes `Retry-After`.

## Account

### Identify the current credential

`GET /v1/me`

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

## Organizations

### List organizations you belong to

`GET /v1/organizations`

- CLI: `salesdogg organizations list`
- MCP tool: `organizations_list`
- Credentials: Session

## Projects

### List projects you can access

`GET /v1/projects`

- CLI: `salesdogg projects list`
- MCP tool: `projects_list`
- Credentials: Session

### Create a project

`POST /v1/projects`

- CLI: `salesdogg projects create`
- MCP tool: `projects_create`
- Credentials: Session

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes |  |
| `organization_id` | body | string | no | Defaults to your only organization, or creates one named after the project. |

### Delete a project and all of its data

`DELETE /v1/projects/{project_id}`

- CLI: `salesdogg projects delete <project_id>`
- MCP tool: `projects_delete`
- Credentials: Session

Needs the owner or admin role in the organization.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `project_id` | path | string | yes |  |

## API keys

### List API keys of the project

`GET /v1/api_keys`

- CLI: `salesdogg api-keys list`
- MCP tool: `api_keys_list`
- Credentials: Session, Secret key

### Create an API key

`POST /v1/api_keys`

- CLI: `salesdogg api-keys create`
- MCP tool: `api_keys_create`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `kind` | body | "secret" \| "public" | yes |  |
| `name` | body | string | no |  |

### Revoke an API key

`DELETE /v1/api_keys/{api_key_id}`

- CLI: `salesdogg api-keys revoke <api_key_id>`
- MCP tool: `api_keys_revoke`
- Credentials: Session, Secret key (sessions need the owner or admin role)

Signed-in users need the owner or admin role in the organization.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `api_key_id` | path | string | yes |  |

## Apps (provider connections)

### List provider connections

`GET /v1/apps`

- CLI: `salesdogg apps list`
- MCP tool: `apps_list`
- Credentials: Session, Secret key

### Connect a payment provider

`POST /v1/apps`

- CLI: `salesdogg apps create`
- MCP tool: `apps_create`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `provider` | body | "stripe" \| "braintree" \| "app_store" \| "play_store" \| "simulator" | yes |  |
| `name` | body | string | no |  |
| `credentials` | body | object | no | Provider secrets. Stored encrypted; never returned. |
| `config` | body | object | no | Non-secret provider settings. |

### Get a provider connection

`GET /v1/apps/{app_id}`

- CLI: `salesdogg apps get <app_id>`
- MCP tool: `apps_get`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_id` | path | string | yes |  |

### Update a provider connection

`PATCH /v1/apps/{app_id}`

- CLI: `salesdogg apps update <app_id>`
- MCP tool: `apps_update`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_id` | path | string | yes |  |
| `name` | body | string | no |  |
| `credentials` | body | object | no |  |
| `config` | body | object | no |  |

### Delete a provider connection and its products

`DELETE /v1/apps/{app_id}`

- CLI: `salesdogg apps delete <app_id>`
- MCP tool: `apps_delete`
- Credentials: Session, Secret key (sessions need the owner or admin role)

Signed-in users need the owner or admin role in the organization.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_id` | path | string | yes |  |

### Import products from the provider

`POST /v1/apps/{app_id}/import_products`

- CLI: `salesdogg apps import-products <app_id>`
- MCP tool: `apps_import_products`
- Credentials: Session, Secret key

Fetches the provider's catalog (Stripe prices, Braintree plans) and creates any products that do not exist yet. Idempotent.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_id` | path | string | yes |  |

## Products

### List products

`GET /v1/products`

- CLI: `salesdogg products list`
- MCP tool: `products_list`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1-100). |
| `starting_after` | query | string | no | Cursor: the `next_cursor` value from the previous page. |
| `app_id` | query | string | no |  |

### Create a product

`POST /v1/products`

- CLI: `salesdogg products create`
- MCP tool: `products_create`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_id` | body | string | yes |  |
| `store_identifier` | body | string | yes |  |
| `type` | body | "subscription" \| "one_time" | no |  |
| `display_name` | body | string | no |  |

### Delete a product

`DELETE /v1/products/{product_id}`

- CLI: `salesdogg products delete <product_id>`
- MCP tool: `products_delete`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `product_id` | path | string | yes |  |

## Entitlements

### List entitlements

`GET /v1/entitlements`

- CLI: `salesdogg entitlements list`
- MCP tool: `entitlements_list`
- Credentials: Session, Secret key

### Create or update an entitlement

`POST /v1/entitlements`

- CLI: `salesdogg entitlements create`
- MCP tool: `entitlements_create`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `lookup_key` | body | string | yes |  |
| `display_name` | body | string | no |  |
| `product_ids` | body | string[] | no |  |

### Attach products to an entitlement

`POST /v1/entitlements/{entitlement}/products`

- CLI: `salesdogg entitlements attach-products <entitlement>`
- MCP tool: `entitlements_attach_products`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entitlement` | path | string | yes | Entitlement id or lookup_key. |
| `product_ids` | body | string[] | yes |  |

### Detach a product from an entitlement

`DELETE /v1/entitlements/{entitlement}/products/{product_id}`

- CLI: `salesdogg entitlements detach-product <entitlement> <product_id>`
- MCP tool: `entitlements_detach_product`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entitlement` | path | string | yes | Entitlement id or lookup_key. |
| `product_id` | path | string | yes |  |

### Delete an entitlement

`DELETE /v1/entitlements/{entitlement}`

- CLI: `salesdogg entitlements delete <entitlement>`
- MCP tool: `entitlements_delete`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entitlement` | path | string | yes | Entitlement id or lookup_key. |

## Offerings

### List offerings

`GET /v1/offerings`

- CLI: `salesdogg offerings list`
- MCP tool: `offerings_list`
- Credentials: Session, Secret key, Public 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`.

### Create or update an offering

`POST /v1/offerings`

- CLI: `salesdogg offerings create`
- MCP tool: `offerings_create`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `lookup_key` | body | string | yes |  |
| `display_name` | body | string | no |  |
| `is_current` | body | boolean | no |  |
| `metadata` | body | object | no |  |
| `packages` | body | object[] | no |  |

### Get an offering

`GET /v1/offerings/{offering}`

- CLI: `salesdogg offerings get <offering>`
- MCP tool: `offerings_get`
- Credentials: Session, Secret key, Public key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `offering` | path | string | yes | Offering id or lookup_key. |

### Delete an offering

`DELETE /v1/offerings/{offering}`

- CLI: `salesdogg offerings delete <offering>`
- MCP tool: `offerings_delete`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `offering` | path | string | yes | Offering id or lookup_key. |

## Customers

### List and search customers

`GET /v1/customers`

- CLI: `salesdogg customers list`
- MCP tool: `customers_list`
- Credentials: Session, Secret key

Newest first. Filters combine with AND. Each customer comes with attributes, aliases and last-seen device details.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1-100). |
| `starting_after` | query | string | no | Cursor: the `next_cursor` value from the previous page. |
| `search` | query | string | no | An app user id or alias (exact, or the start of one), or an exact $email address (case-insensitive). |
| `entitlement` | query | string | no | Only customers with this entitlement (lookup_key) active right now. |
| `active` | query | "true" \| "false" | no | true: customers with any active entitlement; false: customers with none. |
| `platform` | query | string | no | Last seen platform, e.g. ios or android. |
| `country` | query | string | no | Last seen country, ISO 3166-1 alpha-2. |
| `app_version` | query | string | no | Last seen app version. |
| `store` | query | "stripe" \| "braintree" \| "app_store" \| "play_store" \| "simulator" | no | Customers with a subscription or purchase from this store. |
| `attribute_key` | query | string | no | With attribute_value: customers whose attribute equals the value (case-insensitive). Alone: customers that have the attribute. |
| `attribute_value` | query | string | no |  |
| `first_seen_after` | query | string | no |  |
| `first_seen_before` | query | string | no |  |
| `last_seen_after` | query | string | no |  |
| `last_seen_before` | query | string | no |  |

### Create or update a customer

`POST /v1/customers`

- CLI: `salesdogg customers upsert`
- MCP tool: `customers_upsert`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | body | string | yes |  |
| `attributes` | body | object | no | Key -> value. A value may also be `{ value, updated_at_ms }` (RevenueCat format). null or "" deletes the attribute. |

### Set customer attributes

`POST /v1/customers/{app_user_id}/attributes`

- CLI: `salesdogg customers set-attributes <app_user_id>`
- MCP tool: `customers_set_attributes`
- Credentials: Session, Secret key, Public key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | Your own identifier for the user (URL-encoded). An alias works too. |
| `attributes` | body | object | yes | Key -> value. A value may also be `{ value, updated_at_ms }` (RevenueCat format). null or "" deletes the attribute. |

### Attach an anonymous customer to a known user

`POST /v1/customers/identify`

- CLI: `salesdogg customers identify`
- MCP tool: `customers_identify`
- Credentials: Session, Secret key, Public key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `anonymous_id` | body | string | yes |  |
| `app_user_id` | body | string | yes |  |

### Get a customer with entitlements, subscriptions, purchases and attributes

`GET /v1/customers/{app_user_id}`

- CLI: `salesdogg customers get <app_user_id>`
- MCP tool: `customers_get`
- Credentials: Session, Secret key

Server-side only. Client apps check access with GET /v1/customers/{app_user_id}/entitlements. An alias finds its customer.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | Your own identifier for the user (URL-encoded). An alias works too. |

### Delete a customer, their aliases, attributes and purchase history

`DELETE /v1/customers/{app_user_id}`

- CLI: `salesdogg customers delete <app_user_id>`
- MCP tool: `customers_delete`
- Credentials: Session, Secret key (sessions need the owner or admin role)

Signed-in users need the owner or admin role in the organization.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | Your own identifier for the user (URL-encoded). An alias works too. |

### Check what a user has access to

`GET /v1/customers/{app_user_id}/entitlements`

- CLI: `salesdogg customers entitlements <app_user_id>`
- MCP tool: `customers_entitlements`
- Credentials: Session, Secret key, Public key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | Your own identifier for the user (URL-encoded). An alias works too. |

### Grant an entitlement without a purchase

`POST /v1/customers/{app_user_id}/grants`

- CLI: `salesdogg customers grant <app_user_id>`
- MCP tool: `customers_grant`
- Credentials: Session, Secret key

Gives the user an entitlement directly (promotions, support, testing). Creates the customer if needed.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | Your own identifier for the user (URL-encoded). An alias works too. |
| `entitlement` | body | string | yes | Entitlement lookup_key or id. |
| `expires_at` | body | string | no | Omit for a grant that never expires. |

### Revoke granted entitlements

`DELETE /v1/customers/{app_user_id}/grants/{entitlement}`

- CLI: `salesdogg customers revoke-grant <app_user_id> <entitlement>`
- MCP tool: `customers_revoke_grant`
- Credentials: Session, Secret key

Revokes all active grants of the entitlement for this user. Access from purchases is unaffected.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | Your own identifier for the user (URL-encoded). An alias works too. |
| `entitlement` | path | string | yes | Entitlement lookup_key or id. |

## Subscriptions

### List subscriptions

`GET /v1/subscriptions`

- CLI: `salesdogg subscriptions list`
- MCP tool: `subscriptions_list`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1-100). |
| `starting_after` | query | string | no | Cursor: the `next_cursor` value from the previous page. |
| `app_user_id` | query | string | no |  |
| `status` | query | "trialing" \| "active" \| "in_grace_period" \| "past_due" \| "paused" \| "expired" | no |  |
| `environment` | query | "production" \| "sandbox" | no |  |

### Get a subscription

`GET /v1/subscriptions/{subscription_id}`

- CLI: `salesdogg subscriptions get <subscription_id>`
- MCP tool: `subscriptions_get`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `subscription_id` | path | string | yes |  |

## Purchases

### List one-time purchases

`GET /v1/purchases`

- CLI: `salesdogg purchases list`
- MCP tool: `purchases_list`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1-100). |
| `starting_after` | query | string | no | Cursor: the `next_cursor` value from the previous page. |
| `app_user_id` | query | string | no |  |

## Receipts

### Report a purchase made in a client app

`POST /v1/receipts`

- CLI: `salesdogg receipts submit`
- MCP tool: `receipts_submit`
- Credentials: Session, Secret key, Public key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | body | string | yes |  |
| `app_id` | body | string | no | The app (provider connection) the purchase was made through. |
| `provider` | body | "stripe" \| "braintree" \| "app_store" \| "play_store" \| "simulator" | no | Alternative to app_id when the project has exactly one app for this provider. Client SDKs send this. |
| `token` | body | string | yes |  |
| `store_product_identifier` | body | string | no |  |

## Checkout

### Create a hosted checkout page for a user

`POST /v1/checkout_sessions`

- CLI: `salesdogg checkout create`
- MCP tool: `checkout_create`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | body | string | yes |  |
| `product_id` | body | string | yes | Product to sell (a Stripe product). |
| `success_url` | body | string | yes |  |
| `cancel_url` | body | string | no |  |
| `customer_email` | body | string | no |  |
| `trial_days` | body | integer | no |  |

## Events

### List lifecycle events, newest first

`GET /v1/events`

- CLI: `salesdogg events list`
- MCP tool: `events_list`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1-100). |
| `starting_after` | query | string | no | Cursor: the `next_cursor` value from the previous page. |
| `app_user_id` | query | string | no |  |
| `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" | no |  |

## Webhooks

### List webhook endpoints

`GET /v1/webhook_endpoints`

- CLI: `salesdogg webhooks list`
- MCP tool: `webhooks_list`
- Credentials: Session, Secret key

### Register a webhook endpoint

`POST /v1/webhook_endpoints`

- CLI: `salesdogg webhooks create`
- MCP tool: `webhooks_create`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `url` | body | string | yes |  |
| `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"[] | no | Omit to receive all events. |
| `format` | body | "salesdogg" \| "revenuecat" | no |  |
| `authorization_header` | body | string | no | Sent verbatim as the Authorization header of every delivery, as RevenueCat does. |

### Delete a webhook endpoint

`DELETE /v1/webhook_endpoints/{endpoint_id}`

- CLI: `salesdogg webhooks delete <endpoint_id>`
- MCP tool: `webhooks_delete`
- Credentials: Session, Secret key (sessions need the owner or admin role)

Signed-in users need the owner or admin role in the organization.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string | yes |  |

### Send a test event to all enabled endpoints

`POST /v1/webhook_endpoints/{endpoint_id}/test`

- CLI: `salesdogg webhooks test <endpoint_id>`
- MCP tool: `webhooks_test`
- Credentials: Session, Secret key

Emits an event of type "test". Check the outcome with GET /v1/webhook_deliveries.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string | yes |  |

### List webhook delivery attempts, newest first

`GET /v1/webhook_deliveries`

- CLI: `salesdogg webhooks deliveries`
- MCP tool: `webhooks_deliveries`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1-100). |
| `starting_after` | query | string | no | Cursor: the `next_cursor` value from the previous page. |
| `endpoint_id` | query | string | no |  |
| `status` | query | "pending" \| "succeeded" \| "failed" | no |  |

## Metrics

### Headline numbers for the project

`GET /v1/metrics/overview`

- CLI: `salesdogg metrics overview`
- MCP tool: `metrics_overview`
- Credentials: Session, Secret key

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `environment` | query | "production" \| "sandbox" | no |  |

### Daily subscription metrics over a date range

`GET /v1/metrics/timeseries`

- CLI: `salesdogg metrics timeseries`
- MCP tool: `metrics_timeseries`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `from` | query | string | no | First day (YYYY-MM-DD, UTC). Defaults to 29 days before `to`. |
| `to` | query | string | no | Last day (YYYY-MM-DD, UTC). Defaults to today. |
| `environment` | query | "production" \| "sandbox" | no |  |

## Simulator

### Simulate a purchase

`POST /v1/simulator/purchases`

- CLI: `salesdogg simulator purchase`
- MCP tool: `simulator_purchase`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | body | string | yes |  |
| `store_identifier` | body | string | no |  |
| `product_id` | body | string | no |  |
| `type` | body | "subscription" \| "one_time" | no | Only with store_identifier, when the product does not exist yet. Default subscription. |
| `trial_days` | body | integer | no | Start a subscription with a free trial of this length. |
| `billing_period` | body | string | no | ISO 8601 duration of one billing cycle, e.g. P1M, P1Y, P1W. |
| `price_amount` | body | integer | no | Price per billing period in minor units (cents). |
| `currency` | body | string | no |  |

### Move a simulated subscription through its lifecycle

`POST /v1/simulator/subscriptions/{subscription_id}/advance`

- CLI: `salesdogg simulator advance <subscription_id>`
- MCP tool: `simulator_advance`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `subscription_id` | path | string | yes |  |
| `action` | body | "renew" \| "cancel" \| "uncancel" \| "fail_payment" \| "recover" \| "pause" \| "resume" \| "expire" \| "refund" | yes |  |

### Refund a simulated one-time purchase

`POST /v1/simulator/purchases/{purchase_id}/refund`

- CLI: `salesdogg simulator refund-purchase <purchase_id>`
- MCP tool: `simulator_refund_purchase`
- Credentials: Session, Secret key

Marks the purchase refunded, which removes the access it granted.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `purchase_id` | path | string | yes |  |

## Settings

### Get the project's settings

`GET /v1/settings`

- CLI: `salesdogg settings get`
- MCP tool: `settings_get`
- Credentials: Session, Secret key

### Change the project's settings

`PATCH /v1/settings`

- CLI: `salesdogg settings update`
- MCP tool: `settings_update`
- Credentials: Session, Secret key (sessions need the owner or admin role)

Signed-in users need the owner or admin role in the organization.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `restore_behavior` | body | "transfer" \| "keep_with_original" | no |  |

## RevenueCat-compatible subscribers

### Get (or create) a customer in RevenueCat's format

`GET /v1/subscribers/{app_user_id}`

- CLI: `salesdogg subscribers get <app_user_id>`
- MCP tool: `subscribers_get`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | The app user id (or any alias). |

### Delete a customer (RevenueCat format)

`DELETE /v1/subscribers/{app_user_id}`

- CLI: `salesdogg subscribers delete <app_user_id>`
- MCP tool: `subscribers_delete`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | The app user id (or any alias). |

### Update customer attributes in RevenueCat's format

`POST /v1/subscribers/{app_user_id}/attributes`

- CLI: `salesdogg subscribers set-attributes <app_user_id>`
- MCP tool: `subscribers_set_attributes`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | The app user id (or any alias). |
| `attributes` | body | object | yes |  |

### Grant a promotional entitlement (RevenueCat format)

`POST /v1/subscribers/{app_user_id}/entitlements/{entitlement_identifier}/promotional`

- CLI: `salesdogg subscribers grant-promotional <app_user_id> <entitlement_identifier>`
- MCP tool: `subscribers_grant_promotional`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | The app user id (or any alias). |
| `entitlement_identifier` | path | string | yes | Entitlement lookup_key. |
| `duration` | body | "daily" \| "three_day" \| "weekly" \| "two_week" \| "monthly" \| "two_month" \| "three_month" \| "six_month" \| "yearly" \| "lifetime" | no |  |
| `start_time_ms` | body | integer | no |  |
| `end_time_ms` | body | integer | no |  |

### Revoke promotional entitlements (RevenueCat format)

`POST /v1/subscribers/{app_user_id}/entitlements/{entitlement_identifier}/revoke_promotionals`

- CLI: `salesdogg subscribers revoke-promotionals <app_user_id> <entitlement_identifier>`
- MCP tool: `subscribers_revoke_promotionals`
- Credentials: Session, Secret key

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `app_user_id` | path | string | yes | The app user id (or any alias). |
| `entitlement_identifier` | path | string | yes | Entitlement lookup_key. |

## Imports

### Import products, entitlements and offerings from RevenueCat

`POST /v1/imports/revenuecat/catalog`

- CLI: `salesdogg imports revenuecat-catalog`
- MCP tool: `imports_revenuecat_catalog`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `api_key` | body | string | yes | 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 | yes | The RevenueCat project id (proj...). |
| `app_map` | body | object | no | 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. |

### Import one page of customers from RevenueCat

`POST /v1/imports/revenuecat/customers`

- CLI: `salesdogg imports revenuecat-customers`
- MCP tool: `imports_revenuecat_customers`
- Credentials: Session, Secret key (sessions need the owner or admin role)

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `api_key` | body | string | yes | 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 | yes | The RevenueCat project id (proj...). |
| `app_map` | body | object | no | 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 | no | The next_cursor of the previous page. |
| `limit` | body | integer | no | Customers per call. Each takes about 4 RevenueCat requests plus one store lookup per purchase. |
| `include_expired` | body | boolean | no | Also look up subscriptions that have expired, for history. |

## billing

### Get the organization's SalesDogg plan, with an upgrade link on Free

`GET /v1/billing`

- CLI: `salesdogg billing get`
- MCP tool: `billing_get`
- Credentials: Session, Secret key

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