Which API endpoints are RevenueDot extensions?
These endpoints exist only in RevenueDot. They use the same auth, errors and list envelope as REST API v2. The dashboard is built on them, so everything the dashboard does, a script or an AI agent can do too.
Base URL: your server, for example http://localhost:8787 or https://revenuedot.example.com. The examples read REVENUEDOT_URL, PUBLIC_KEY, SECRET_KEY and PROJECT_ID from your shell.
Operations on this page (300)#
- Dashboard auth: Whether sign-up is open, Create a dashboard account, Sign in, Sign out, The signed-in user and their projects, Update account settings, Email a password reset link, Check a password reset link, Set a new password from a reset link, Confirm an email address, Send a new confirmation email, Look up an invite, Accept an invite
- Account settings: Finish a sign-in with a two-factor code, Sign out of every session, Change the account's email, Cancel a waiting email change, Confirm an email change, Change the password, List signed-in sessions, Sign out one session, Sign out every other session, Start two-factor setup, Turn two-factor authentication on, Turn two-factor authentication off, Make new recovery codes, List OAuth tokens you granted, Revoke an OAuth token, List connected Stripe accounts, List your projects with role and plan, What deleting the account would do, Delete the account, Get notification choices, Open an email's unsubscribe link, Unsubscribe from one email, Open a product email's unsubscribe link, Unsubscribe from product emails, Open a welcome email's path link, Record that the reader is switching from RevenueCat, Open a one-click answer link from an email, Record a one-click answer from an email, Choose a project's emails, The display currency's exchange rate
- Members and invites: List open invites, Invite someone by email, Resend an invite, Revoke an invite, Change a member's role, Remove a member, or leave the project
- Project settings: Get a project with its settings, Update a project's name, transfer behaviour and sandbox testing access, Delete a project and everything in it, Get the Customer Center configuration of the project, Set the Customer Center configuration, Transfer project ownership to an admin
- Brand: Colour and gradient presets, Replace colour or gradient presets
- Blocked customers: List blocked app user ids, Block an app user id, Is this app user id blocked?, Unblock an app user id
- Verified Metrics: Verified Metrics page settings, Save Verified Metrics page settings, Whether a slug is free, The 12 monthly points of each metric, Set or clear the page's custom domain, Check the custom domain's DNS, Publish the page, Unpublish the page, The public Verified Metrics page, The page's numbers as JSON, The page's project icon, The page's 1200×630 link preview
- Auth: Auth on or off, Turn Auth or anonymous sign-in on or off, List identity providers, Add a Firebase or OpenID Connect provider, Get an identity provider, Update or turn off an identity provider, Delete an identity provider, Check an ID token without signing in, List signed-in identities, Read a signed-in user's balances and entitlements by identity, Unlink an identity and sign it out
- Store setup: Store setup state of an app, Download the sample app for this app, Check store credentials with the store, Check the App Store Connect API key with Apple, Paddle: create the notification destination (Apply in Paddle), Extend every active App Store subscriber of a product, Status of a mass extension, Setup health
- Store prices and product editor: List store prices, Read an app's store prices again, Download a product file, List product files, Upload a product file, Get a product file, Change a product file's options, Discard a product file, Commit a product file to the store, Retry the failed rows of a product file
- API keys: List secret keys, Create a secret key, Delete a secret key
- Webhook deliveries: Send a TEST event to one webhook, Whether each webhook is enabled, Delivery log of a webhook, One delivery: what was sent and every attempt, Retry a delivery now
- Integrations: What each integration needs, List integrations, Connect an integration, Get an integration, Update, enable or disable an integration, Disconnect an integration, Send a TEST event to one integration, Delivery log of an integration, One delivery with every attempt, Retry a delivery now, Queue failed or skipped deliveries again
- Ads: Ads Overview: ad revenue, impressions, eCPM and breakdowns, List reward rules in the order they are checked, Create a reward rule, Reorder reward rules, Update, turn on or turn off a reward rule, Delete a reward rule, The rewards ledger, Send a test reward, AdMob connection, loaded ad units and the URLs to paste, Disconnect AdMob, Start Google sign-in for AdMob, Finish Google sign-in for AdMob, Load AdMob ad units now, Customers and revenue by Apple Search Ads campaign, Load campaign names from Apple Search Ads, AdMob server-side verification callback, Google's redirect after AdMob sign-in
- Data exports: List scheduled data exports, Create a scheduled data export, List export columns, Download a file of an email export, Get a data export, Update a data export, Delete a data export, Run an export now, Check the bucket and credentials, Run history
- Refund Control: Get policies and settings, Save settings and the ordered policies, Refund rate, amounts and counts, List refund requests, newest first
- Retention: List Customer Center retention offers, Create a retention offer, Update a retention offer, Delete a retention offer, Get Apple Retention Messaging settings, Save messages, defaults and real-time rules, Upload to Apple and register the real-time URL, Apple's real-time Get Retention Message call
- Support: List Customer Center tickets, newest first, Get a ticket, Close or reopen a ticket, What a help desk sidebar shows about a customer, Look customers up by email, Intercom inbox app: the customer's subscription as Canvas Kit components
- Payment recovery: Get payment recovery settings, Replace payment recovery settings, At risk, emails sent, recovered revenue, List recovery cases, Send a test recovery email, Send the emails that are due now, Email link: opens the place to fix the payment, Customer Center link (customer info's management_url), Email a one-time link to the Stripe customer portal, One-time link page (a button; opening it spends nothing), Use the one-time link: a Stripe portal session made now, Where the Stripe portal returns to, Unsubscribe page (asks first), Unsubscribe (also RFC 8058 one-click)
- Win-back: List win-back campaigns, Create a win-back campaign, Get a campaign with stats and recent emails, Update, start or pause a campaign, Delete a campaign, Who would get the email now, Send a test email, Send now, Email button: records the click and redirects to the offer, Open-tracking image (campaigns with track_opens), Unsubscribe page (asks first), Unsubscribe (also RFC 8058 one-click)
- Customer lists: Customers in a list, with the summary cards, Export a list as CSV
- Event log: Event log, Transaction feed
- Test Store: Simulate a Test Store purchase or lifecycle
- Dashboard data: Daily history of an overview metric, Dashboard rows for customers, Overview cards summed across your projects, Transactions across your projects
- Data moves: List the project's exports, Export the whole project, Get the latest export, Get an export, Delete an export's files now, Do the next slice of an export now, Download an archive, Get the project's move state, Move the project to another server (run by this server), Do the next step of the move now, Switch to the target, Pause writes for the last copy, Forward the paused project to its new server, Serve the project here again, Create an import token (Receive a project), List your moves into this server, Check or start loading an archive, Get an import, List the project's collaborators to invite, Verify the copy, Put the project live here
- Cloud billing: The account's plan, go-live stage, usage and invoices, Start Pro with Stripe Checkout, Open the Stripe Customer Portal, Webhook of RevenueDot's own Stripe account
- Migration import: Import customers with their purchases, Keep an app's existing SDK key, What still needs attention after an import
- Web billing: List web discounts with their settings, Get the Web page: providers and checklist, Get a Stripe app's web config, Save a Stripe app's web config, List a Stripe app's web products, Create a web product in Stripe, Get the project's web address and custom domain, Change the project's slug or custom domain, Check the custom domain's DNS records
- Stripe Connect: Get a Stripe app's Connect status, Start connecting a Stripe account, Finish connecting (from the callback page), Disconnect the Stripe account
- Purchase links: List purchase links, Create a purchase link, Get a purchase link, Update a purchase link, Delete a purchase link
- Funnels: List funnels, Create a funnel, Is Build with AI available?, Build a funnel with AI, Get a funnel, Update a funnel's draft, Delete a funnel, Publish a funnel, Unpublish a funnel, Get a funnel's analytics, Get what the builder's preview needs
- Hosted pages: A purchase link or a published funnel, The success page after Stripe Checkout, The success page of the iOS SDK's hosted checkout, The cancel page of the iOS SDK's hosted checkout, A redemption link page, Start a checkout from a page, Check a discount code from a page, Record a funnel event from a page
- RevenueDot AI: What RevenueDot AI can do here, Set what RevenueDot AI may do in the project, List your conversations, newest first, Start a conversation, Get a conversation with its messages, Rename a conversation, Delete a conversation, Send a message and stream the answer (self-host), Resume the answer being written, Stop the answer being written, Attach an image or a .storekit file, Read an attachment back, Read a .storekit file, Suggestions for @ mentions, The project's first-sale card, Hide the first-sale card on the Overview
- Attribution: Get a customer's attribution, Revenue by campaign
- Benchmarks: The project's values against peers, Whether the project shares benchmarks, Share or stop sharing anonymized benchmarks
- Growth insights: This week's growth insights, Write this week's insights now, The digest's opt-out page, Turn the weekly digest off (one click)
- Share cards: Public chart page, Public chart preview image, Public chart as SVG, Public first-sale page or image
- OAuth for MCP clients: OAuth authorization server metadata, Register an OAuth client, Consent screen, Submit the consent decision, Exchange a code for an access token
Dashboard auth#
Sign-up, sign-in, password reset, email confirmation, invites and account settings for the dashboard. The session cookie also authorizes REST API v2.
Whether sign-up is open#
GET /auth/config · Auth: none · RevenueDot extension
What the sign-in page needs before it shows a form.
Example request
curl -s "$REVENUEDOT_URL/auth/config"Responses
- 200: The config.
Example 200 response:
{
"edition": "self-hosted",
"signup": "closed"
}Create a dashboard account#
POST /auth/signup · Auth: none · RevenueDot extension
Creates the user and a first project, and sets the rd_session cookie (30 days; Secure over https). On a self-hosted server only the first account (the owner) can sign up, unless the server runs with REVENUEDOT_ALLOW_SIGNUP=true.
With invite_token (from an invite link), the account joins the inviting project instead of getting a new one, and sign-up works even where it is closed. The email must be the invited address; the account counts as verified. On RevenueDot Cloud, an account without an invite gets an email with a confirmation link (valid 24 hours).
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | |
password |
string | yes | |
name |
string | no | |
project_name |
string | no | Default: My project. Ignored with invite_token. |
invite_token |
string | no | RevenueDot extension. The token from an invite link (/invite?token=...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/signup" \
-H "Content-Type: application/json" -d '{"email":"dev@example.com","password":"change-me-please","project_name":"My app"}'Responses
- 201: Signed up and signed in. With an invite,
project_idis the project joined. - 400: Invalid email or password, or an invite that is not valid (
invite_invalid) or for another address (invite_email_mismatch). - 403: Sign-up is closed: the server has an owner already.
- 409: The email is taken.
Example 201 response:
{
"ok": true
}Sign in#
POST /auth/login · Auth: none · RevenueDot extension
The dashboard's sign-in. A request with an Authorization header is an app's Auth sign-in instead: see POST /v1/auth/login, which takes the same body at this path for the RevenueCat SDKs' token login. With two-factor authentication on, a correct password answers two_factor_required: true and a challenge instead of a session; finish with POST /auth/login/2fa.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | |
password |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/login"Responses
- 200: Signed in (
rd_sessionis set), or the two-factor step. - 400: Missing fields.
- 401: Wrong email or password.
Example 200 response:
{
"ok": true
}Sign out#
POST /auth/logout · Auth: dashboard session · RevenueDot extension
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/logout"Responses
- 200: Signed out.
Example 200 response:
{
"ok": true
}The signed-in user and their projects#
GET /auth/me · Auth: dashboard session · RevenueDot extension
Example request
curl -s "$REVENUEDOT_URL/auth/me"Responses
- 200: The user.
- 401: Not signed in.
Example 200 response:
{
"user": {
"id": "usr_8k2m4q",
"email": "dev@example.com",
"name": "Dana",
"email_verified": true,
"alert_emails": true
},
"account": {
"edition": "cloud",
"plan": "none",
"gate": {
"stage": "building",
"live_at": null,
"grace_ends_at": null
},
"project_gates": {},
"billing_ready": true,
"billing_status": "none",
"email_verification_required": false
},
"projects": []
}Update account settings#
POST /auth/me · Auth: dashboard session · RevenueDot extension
The display name, whether the user gets alert emails and the weekly growth insights digest for projects they administer, and the Interface and Date and region preferences. Send only the fields to change. A null or empty name clears it.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | no | |
alert_emails |
boolean | no | False stops alert emails for every project. |
integration_alert_emails |
boolean | no | False stops integration failure alerts only. |
insights_emails |
boolean | no | False stops the weekly growth insights digest for every project. |
theme |
system, light, dark |
no | |
tint |
string or null | no | #RRGGBB, or null for the default gold. |
week_start |
integer | no | |
display_currency |
USD, EUR, GBP, AUD, CAD, JPY, BRL, KRW, CNY, MXN, SEK, PLN, NZD, CHF |
no |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/me" \
-H "Content-Type: application/json" -d '{"alert_emails":false,"week_start":0,"display_currency":"EUR"}'Responses
- 200: The updated user.
- 400: Invalid field.
- 401: Not signed in.
Example 200 response:
{
"user": {
"id": "usr_8k2m4q",
"email": "dev@example.com",
"name": "Dana",
"email_verified": true,
"alert_emails": false
}
}Email a password reset link#
POST /auth/password/forgot · Auth: none · RevenueDot extension
Always answers 200 with the same body, whether or not an account uses the address, so the answer does not reveal who has an account. If one does, it gets a link to /reset-password that works once and expires after 1 hour.
Limits: 5 requests per IP address per 15 minutes (then 429), and 3 emails per address per hour (further requests answer 200 but send nothing). See I forgot my password.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/password/forgot" \
-H "Content-Type: application/json" -d '{"email":"dev@example.com"}'Responses
- 200: Accepted.
- 400: Not a valid email address.
- 429: Too many requests from this IP address.
Example 200 response:
{
"ok": true,
"message": "If an account uses this email, we sent it a link to reset the password. The link expires in 1 hour."
}Check a password reset link#
POST /auth/password/check · Auth: none · RevenueDot extension
Tells the reset page whether the link still works before the user types a new password. Does not use up the link.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The token from the reset link. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/password/check"Responses
- 200: Whether the link works.
- 400: Missing token.
Example 200 response:
{
"valid": false,
"reason": "expired",
"message": "This link has expired. Ask for a new one."
}Set a new password from a reset link#
POST /auth/password/reset · Auth: none · RevenueDot extension
Sets the password, signs the user out on every device, marks the email as confirmed (the link proved the inbox) and signs this browser in with a new rd_session cookie. Every other open reset link of the user stops working, and so do two-factor sign-ins begun with the old password. With two-factor authentication on, it answers two_factor_required: true and a challenge instead of signing in; finish with POST /auth/login/2fa.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | yes | |
password |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/password/reset" \
-H "Content-Type: application/json" -d '{"token":"…","password":"a-new-long-password"}'Responses
- 200: Password changed and signed in, or the two-factor step.
- 400: The password is too short or too long, or the link is not valid (
token_invalidwith areason).
Example 200 response:
{
"ok": true
}Confirm an email address#
POST /auth/email/verify · Auth: none · RevenueDot extension
RevenueDot Cloud only: the link in the confirmation email sent at sign-up (valid 24 hours, works once). Self-hosted servers treat every account as confirmed.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The token from the confirmation link. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/email/verify"Responses
- 200: Confirmed.
- 400: The link is not valid (
token_invalidwith areason).
Example 200 response:
{
"ok": true,
"email": "dev@example.com"
}Send a new confirmation email#
POST /auth/email/verify/resend · Auth: dashboard session · RevenueDot extension
Up to 5 per user per hour. An account that is already confirmed gets already_verified: true and no email.
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/email/verify/resend"Responses
- 200: Sent, or already confirmed.
- 401: Not signed in.
- 429: Too many emails this hour.
- 502: The mail server did not accept the email.
Example 200 response:
{
"ok": true,
"email": "dev@example.com"
}Look up an invite#
GET /auth/invites/{token} · Auth: none · RevenueDot extension
What the invite page shows: the project, the role, who sent it and whether the invited address has an account already (sign in and accept, or sign up with invite_token).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The token from the invite link (/invite?token=...). |
Example request
curl -s "$REVENUEDOT_URL/auth/invites/$TOKEN"Responses
- 200: The invite.
- 404: Not valid, expired, already accepted or revoked.
Example 200 response:
{
"object": "invite",
"email": "sam@example.com",
"role": "developer",
"project": {
"id": "proj18pzzkao",
"name": "My app"
},
"invited_by": {
"name": "Dana",
"email": "dev@example.com"
},
"expires_at": 1791405714000,
"account_exists": false
}Accept an invite#
POST /auth/invites/{token}/accept · Auth: dashboard session · RevenueDot extension
For a user who already has an account, signed in with the invited address. Adds them to the project with the invite's role; someone who is already a member keeps their role. Also marks their email as confirmed.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The token from the invite link (/invite?token=...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/invites/$TOKEN/accept"Responses
- 200: Joined.
- 401: Not signed in.
- 403: Signed in with another address.
- 404: The invite is no longer valid.
Example 200 response:
{
"ok": true,
"project_id": "proj18pzzkao"
}Account settings#
Everything that belongs to the signed-in person: email change, password, sessions, two-factor authentication, OAuth tokens, Stripe accounts, projects with plans, deletion, notification emails and the display currency's rate. Dashboard session only. See Account settings.
Finish a sign-in with a two-factor code#
POST /auth/login/2fa · Auth: none · RevenueDot extension
With two-factor authentication on, POST /auth/login (and POST /auth/password/reset) answer { "two_factor_required": true, "challenge": "…" } without a session. Send the challenge here with a code from the authenticator app or a recovery code. The challenge works once and expires after 10 minutes. A code is never accepted twice.
Limits: 5 attempts per challenge (then sign in again), and 10 wrong codes per account in 15 minutes, counted with the code checks of Account settings (then 429 until 15 minutes after the first). A right code does not count. A password change or reset ends the challenges that began with the old password.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
challenge |
string | yes | |
code |
string | no | A 6-digit code from the authenticator app. Needed when two-factor authentication is on. |
recovery_code |
string | no | A recovery code (abcde-fghjk) instead of code. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/login/2fa" \
-H "Content-Type: application/json" -d '{"challenge":"…","code":"123456"}'Responses
- 200: Signed in;
rd_sessionis set. A recovery code also answers how many are left. - 400: The challenge expired, was used, or two-factor was turned off (
challenge_invalidwith areason), or a missing challenge (invalid_request). - 401: Wrong code.
- 429: Too many attempts.
Example 200 response:
{
"ok": true
}Sign out of every session#
POST /auth/logout/all · Auth: dashboard session · RevenueDot extension
Ends every session of the signed-in person, this one included.
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/logout/all"Responses
- 200: Signed out everywhere.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
Example 200 response:
{
"ok": true,
"sessions_revoked": 3
}Change the account's email#
POST /auth/email/change · Auth: dashboard session · RevenueDot extension
Emails a link (/confirm-email?token=…, 24 hours, works once) to the new address and a notice to the current one. The account keeps its address until the link is used with POST /auth/email/change/confirm. A new request replaces an open one. Needs the current password, and code when two-factor authentication is on. 5 requests per hour.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
new_email |
string | yes | |
password |
string | no | The current password. |
code |
string | no | With two-factor authentication on. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/email/change" \
-H "Content-Type: application/json" -d '{"new_email":"dana@newcompany.com","password":"current-password"}'Responses
- 200: The link was sent.
- 400: Invalid address, the same address, a wrong password or code.
- 401: Not signed in.
- 403: Single sign-on manages one of the addresses (
sso_required), or a write from another site. - 409: Another account uses the address.
- 429: Too many attempts.
- 502: The email could not be sent.
Cancel a waiting email change#
DELETE /auth/email/change · Auth: dashboard session · RevenueDot extension
The link sent to the new address stops working.
Example request
curl -s -X DELETE "$REVENUEDOT_URL/auth/email/change"Responses
- 200: Cancelled.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
Example 200 response:
{
"ok": true,
"pending_email": null
}Confirm an email change#
POST /auth/email/change/confirm · Auth: none · RevenueDot extension
The link from the confirmation email; works in any browser. The dashboard's /confirm-email page sends this only when the person clicks Confirm new email, never on load, so a mail scanner that opens the link changes nothing. The account moves to the new address, which counts as confirmed, and every link sent to the old address stops working. The old address gets a notice.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/email/change/confirm"Responses
- 200: The account uses the new address.
- 400: The link is not valid (
token_invalidwith areason). - 409: Another account started using the address, even a moment before (
email_taken).
Example 200 response:
{
"ok": true,
"email": "dana@newcompany.com"
}Change the password#
POST /auth/password/change · Auth: dashboard session · RevenueDot extension
Needs the current password (10 password checks per 15 minutes, counted with two-factor setup, email change and deletion, then 429). Every other session is signed out, open password reset links and half-done two-factor sign-ins stop working, and the account gets an email.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
current_password |
string | yes | |
new_password |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/password/change"Responses
- 200: Changed.
- 400: Wrong current password (
invalid_password), a short password, the same password, or an account without one (no_password). - 401: Not signed in.
- 403: The account must sign in with single sign-on (
sso_required), or a write from another site. - 429: Too many attempts.
Example 200 response:
{
"ok": true,
"sessions_revoked": 1
}List signed-in sessions#
GET /auth/sessions · Auth: dashboard session · RevenueDot extension
This browser first, then the others by last activity.
Example request
curl -s "$REVENUEDOT_URL/auth/sessions"Responses
- 200: Sessions.
- 401: Not signed in.
Sign out one session#
DELETE /auth/sessions/{session_id} · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
session_id |
string | yes | The id from the list. |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/auth/sessions/$SESSION_ID"Responses
- 200: Signed out.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 404: Already ended.
Sign out every other session#
POST /auth/sessions/revoke_others · Auth: dashboard session · RevenueDot extension
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/sessions/revoke_others"Responses
- 200: Signed out.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
Example 200 response:
{
"ok": true,
"sessions_revoked": 2
}Start two-factor setup#
POST /auth/2fa/setup · Auth: dashboard session · RevenueDot extension
A new TOTP secret (RFC 6238: SHA-1, 6 digits, 30 seconds), sealed at rest and answered once as base32 and an otpauth:// URI for a QR code. Two-factor stays off until POST /auth/2fa/enable gets a code from it. Needs the current password.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
password |
string | no | The current password. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/2fa/setup"Responses
- 200: The secret.
- 400: Wrong password.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 409: Two-factor is already on.
- 429: Too many attempts.
Example 200 response:
{
"object": "two_factor_setup",
"secret": "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP",
"otpauth_url": "otpauth://totp/RevenueDot:dana%40example.com?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=RevenueDot&algorithm=SHA1&digits=6&period=30",
"issuer": "RevenueDot",
"account": "dana@example.com",
"digits": 6,
"period": 30,
"algorithm": "SHA1"
}Turn two-factor authentication on#
POST /auth/2fa/enable · Auth: dashboard session · RevenueDot extension
Checks a code from the pending secret, turns two-factor on and answers 10 recovery codes once (stored as SHA-256; each works once). The account gets an email.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/2fa/enable" \
-H "Content-Type: application/json" -d '{"code":"123456"}'Responses
- 200: On.
- 400: Wrong or missing code (
invalid_code), or an invalid body (invalid_request). - 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 409: Already on, or no setup to finish.
- 429: Too many attempts.
Turn two-factor authentication off#
POST /auth/2fa/disable · Auth: dashboard session · RevenueDot extension
Needs a current code or a recovery code. The account gets an email.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | no | A 6-digit code from the authenticator app. Needed when two-factor authentication is on. |
recovery_code |
string | no | A recovery code (abcde-fghjk) instead of code. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/2fa/disable"Responses
- 200: Off.
- 400: Wrong or missing code (
invalid_code), or an invalid body (invalid_request). - 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 409: Two-factor is off.
- 429: Too many attempts.
Example 200 response:
{
"ok": true,
"enabled": false
}Make new recovery codes#
POST /auth/2fa/recovery_codes · Auth: dashboard session · RevenueDot extension
10 new codes, answered once; the old ones stop working. Needs a current code or a recovery code.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | no | A 6-digit code from the authenticator app. Needed when two-factor authentication is on. |
recovery_code |
string | no | A recovery code (abcde-fghjk) instead of code. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/2fa/recovery_codes"Responses
- 200: New codes.
- 400: Wrong or missing code (
invalid_code), or an invalid body (invalid_request). - 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 409: Two-factor is off.
- 429: Too many attempts.
List OAuth tokens you granted#
GET /auth/oauth_tokens · Auth: dashboard session · RevenueDot extension
Keys made by POST /oauth/token after you clicked Allow access (ChatGPT, Claude, Cursor …), in any project. Keys granted before this list existed are on each project's API keys page.
Example request
curl -s "$REVENUEDOT_URL/auth/oauth_tokens"Responses
- 200: Tokens.
- 401: Not signed in.
Revoke an OAuth token#
DELETE /auth/oauth_tokens/{key_id} · Auth: dashboard session · RevenueDot extension
Deletes the key; the app gets 401 on its next call. The project's audit log records it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
key_id |
string | yes |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/auth/oauth_tokens/$KEY_ID"Responses
- 200: Revoked.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 404: Already revoked, or not yours.
List connected Stripe accounts#
GET /auth/stripe_accounts · Auth: dashboard session · RevenueDot extension
Stripe accounts connected with Connect with Stripe to Stripe apps in your projects (masked), and the Stripe apps you could connect. Connecting and disconnecting happen per app.
Example request
curl -s "$REVENUEDOT_URL/auth/stripe_accounts"Responses
- 200: Accounts.
- 401: Not signed in.
List your projects with role and plan#
GET /auth/account/projects · Auth: dashboard session · RevenueDot extension
Owned projects first. plan is the owner's RevenueDot Cloud plan (pro Pro, enterprise Enterprise, or none No plan), or self_hosted Self-hosted.
Example request
curl -s "$REVENUEDOT_URL/auth/account/projects"Responses
- 200: Projects.
- 401: Not signed in.
What deleting the account would do#
GET /auth/account/delete · Auth: dashboard session · RevenueDot extension
Whether deletion is allowed now; if not, why (ownership_transfer_required with the projects, billing_active, or an enterprise refusal); if so, which projects are deleted with it and which are left.
Example request
curl -s "$REVENUEDOT_URL/auth/account/delete"Responses
- 200: The check.
- 401: Not signed in.
Delete the account#
POST /auth/account/delete · Auth: dashboard session · RevenueDot extension
Refused (409) while you own a project with other members or are the last admin of one (ownership_transfer_required), while Pro is active (billing_active), or when an enterprise organization still needs you as its owner (extension_refused). Deletes the account, its sessions, links, recovery codes, preferences and AI conversations, the OAuth keys listed by GET /auth/oauth_tokens, and the projects where you are the only member. Audit log entries stay, and every project you leave gets a collaborator_account_deleted entry with your email. Sends a confirmation email and clears the cookie.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | Your email, typed to confirm. |
password |
string | no | Needed when the account has a password. |
code |
string | no | A 6-digit code from the authenticator app. Needed when two-factor authentication is on. |
recovery_code |
string | no | A recovery code (abcde-fghjk) instead of code. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/account/delete" \
-H "Content-Type: application/json" -d '{"email":"dana@example.com","password":"current-password"}'Responses
- 200: Deleted.
- 400: The typed email does not match (
confirmation_mismatch), or a wrong password or code. - 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 409: Not now: see the type.
- 429: Too many attempts.
Example 200 response:
{
"ok": true,
"deleted": true,
"projects_deleted": []
}Get notification choices#
GET /auth/notifications · Auth: dashboard session · RevenueDot extension
Alert emails (on by default), and per project the weekly summary, experiment results and revenue anomaly alerts, which are off until you turn them on.
Example request
curl -s "$REVENUEDOT_URL/auth/notifications"Responses
- 200: Choices.
- 401: Not signed in.
Open an email's unsubscribe link#
GET /auth/notifications/unsubscribe/{token} · Auth: none · RevenueDot extension
An HTML page with an Unsubscribe button. Opening the link changes nothing, because mail scanners open links too.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | From the email's Unsubscribe link or its List-Unsubscribe header. |
Example request
curl -s "$REVENUEDOT_URL/auth/notifications/unsubscribe/$TOKEN"Responses
- 200: The page.
- 404: Unknown link (an HTML page).
Unsubscribe from one email#
POST /auth/notifications/unsubscribe/{token} · Auth: none · RevenueDot extension
Turns off the email this link came with (the weekly summary, experiment results or revenue anomaly alerts) for that one project, without a session. Each such email carries List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click, so mail apps can send this POST themselves (RFC 8058).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/notifications/unsubscribe/$TOKEN"Responses
- 200: Unsubscribed (an HTML page).
- 404: Unknown link (an HTML page).
Open a product email's unsubscribe link#
GET /auth/journeys/unsubscribe/{token} · Auth: none · RevenueDot extension
An HTML page with an Unsubscribe button for the onboarding emails, tips and product news. Opening the link changes nothing, because mail scanners open links too.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | From the Unsubscribe link of an onboarding or product email, or its List-Unsubscribe header. |
Example request
curl -s "$REVENUEDOT_URL/auth/journeys/unsubscribe/$TOKEN"Responses
- 200: The page.
- 404: Unknown link (an HTML page).
Unsubscribe from product emails#
POST /auth/journeys/unsubscribe/{token} · Auth: none · RevenueDot extension
Stops the onboarding emails, tips and product news for the account the link was sent to, without a session. Security, billing and alert emails still arrive. Each such email carries List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click, so mail apps can send this POST themselves (RFC 8058). Turn the emails back on in Account settings → Notifications.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/journeys/unsubscribe/$TOKEN"Responses
- 200: Unsubscribed (an HTML page).
- 404: Unknown link (an HTML page).
Open a welcome email's path link#
GET /auth/journeys/path/{token} · Auth: none · RevenueDot extension
With path=revenuecat, an HTML page with a Yes, show me how to switch button. Opening the link changes nothing, because mail scanners open links too. Any other path redirects to the quickstart, which is where links from older emails land.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | From the welcome email's link. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
path |
new, revenuecat |
no | revenuecat for moving from RevenueCat; anything else opens the quickstart. |
Example request
curl -s "$REVENUEDOT_URL/auth/journeys/path/$TOKEN"Responses
- 200: The page.
- 302: Redirect to the quickstart.
- 404: Unknown link (an HTML page).
Record that the reader is switching from RevenueCat#
POST /auth/journeys/path/{token} · Auth: none · RevenueDot extension
Records the switching path for the account the email was sent to, without a session, so later emails are the switching steps (import, a side-by-side run, turning RevenueCat off) and not the new-app ones. Then redirects to the migration guide.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/journeys/path/$TOKEN"Responses
- 302: Redirect to the migration guide.
- 404: Unknown link (an HTML page).
Open a one-click answer link from an email#
GET /auth/journeys/feedback/{token} · Auth: none · RevenueDot extension
An HTML page that shows the answer with a comment box and a Send button. Opening the link records nothing, because mail scanners open links too.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | From the rating or reason link of an onboarding or Pro email. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
kind |
nps, cancel |
yes | nps for a 0 to 10 rating, cancel for the reason for leaving Pro. |
value |
string | yes | The answer: a whole number from 0 to 10 for nps, or a reason of at most 80 characters for cancel. |
Example request
curl -s "$REVENUEDOT_URL/auth/journeys/feedback/$TOKEN"Responses
- 200: The page.
- 404: Unknown link or invalid answer (an HTML page).
Record a one-click answer from an email#
POST /auth/journeys/feedback/{token} · Auth: none · RevenueDot extension
Records the answer (form fields kind, value and an optional comment of up to 2,000 characters) for the account the email was sent to, without a session. The latest answer of each kind replaces the earlier one.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/journeys/feedback/$TOKEN"Responses
- 200: Recorded (an HTML page).
- 404: Unknown link or invalid answer (an HTML page).
Choose a project's emails#
PUT /auth/notifications/{project_id} · Auth: dashboard session · RevenueDot extension
Send only the fields to change. Any member of the project may choose.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
weekly_summary |
boolean | no | The weekly summary email on the first day of the person's week. |
experiment_results |
boolean | no | An email when an experiment has enough data and when it ends. |
anomaly_alerts |
boolean | no | Daily revenue anomaly alerts. |
anomaly_sensitivity |
low, medium, high |
no |
Example request
curl -s -X PUT "$REVENUEDOT_URL/auth/notifications/$PROJECT_ID" \
-H "Content-Type: application/json" -d '{"weekly_summary":true,"anomaly_alerts":true,"anomaly_sensitivity":"medium"}'Responses
- 200: Saved.
- 400: Invalid field.
- 401: Not signed in.
- 403: A write from another site (Sec-Fetch-Site: cross-site or same-site).
- 404: Not a member of the project.
The display currency's exchange rate#
GET /auth/fx · Auth: dashboard session · RevenueDot extension
Units of the currency per 1 USD on the latest day with a European Central Bank reference rate (cached, with bundled rates when the source cannot be reached). The dashboard multiplies USD amounts by it.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
currency |
string | no | Default: your display currency. One of USD, EUR, GBP, AUD, CAD, JPY, BRL, KRW, CNY, MXN, SEK, PLN, NZD, CHF. |
Example request
curl -s "$REVENUEDOT_URL/auth/fx"Responses
- 200: The rate.
- 400: Unknown currency.
- 401: Not signed in.
- 502: No exchange rate for that currency right now.
Example 200 response:
{
"object": "fx_rate",
"base": "USD",
"currency": "EUR",
"rate": 0.8531,
"date": "2026-10-01",
"source": "ecb"
}Members and invites#
Invite people to a project by email, change their role, remove them. Dashboard session only.
List open invites#
GET /v2/projects/{project_id}/invites · Auth: dashboard session · RevenueDot extension · Permissions: project_configuration:collaborators:read
Invites nobody has accepted or revoked, oldest first. Expired ones stay listed so an admin can resend them. Needs a dashboard session: secret API keys cannot manage members.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/invites"Responses
- 200: Open invites. Returns a list of Invite.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "invite",
"id": "inv_4f8k2m9q1x7z",
"email": "sam@example.com",
"role": "developer",
"status": "pending",
"invited_by": "usr_8k2m4q",
"created_at": 1790800914012,
"last_sent_at": 1790800914012,
"expires_at": 1791405714012
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/invites"
}Invite someone by email#
POST /v2/projects/{project_id}/invites · Auth: dashboard session · RevenueDot extension
Admins only. Emails a link that lasts 7 days. Inviting an address that already has an open invite replaces it: the role changes, a new link goes out and the old one stops working. See Invite your team.
On RevenueDot Cloud the admin needs a confirmed email address. A project can send 50 invites (including resends) per day; after that the answer is 429. email_sent is false when the mail server refused the email; the invite still exists and can be resent.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | |
role |
admin, developer, viewer |
yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/invites" \
-H "Content-Type: application/json" -d '{"email":"sam@example.com","role":"developer"}'Responses
- 201: The invite.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
Example 201 response:
{
"object": "invite",
"id": "inv_4f8k2m9q1x7z",
"email": "sam@example.com",
"role": "developer",
"status": "pending",
"invited_by": "usr_8k2m4q",
"created_at": 1790800914012,
"last_sent_at": 1790800914012,
"expires_at": 1791405714012,
"email_sent": true
}Resend an invite#
POST /v2/projects/{project_id}/invites/{invite_id}/actions/resend · Auth: dashboard session · RevenueDot extension
Admins only. Sends a new link valid for 7 more days; the old link stops working. Works on expired invites. Counts toward the 50 invites per project per day.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
invite_id |
string | yes | Invite id (inv_...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/invites/$INVITE_ID/actions/resend"Responses
- 200: The invite.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
Revoke an invite#
DELETE /v2/projects/{project_id}/invites/{invite_id} · Auth: dashboard session · RevenueDot extension
Admins only. The link stops working at once.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
invite_id |
string | yes | Invite id (inv_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/invites/$INVITE_ID"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "invite",
"id": "…",
"deleted_at": 1790801342625
}Change a member's role#
POST /v2/projects/{project_id}/collaborators/{user_id} · Auth: dashboard session · RevenueDot extension
Admins only. A project always keeps at least one admin, so the last admin cannot be demoted (400). The response uses RevenueCat's role names: viewer comes back as read_only.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
user_id |
string | yes | The member's user id (the collaborator id). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
role |
admin, developer, viewer |
yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/collaborators/$USER_ID" \
-H "Content-Type: application/json" -d '{"role":"viewer"}'Responses
- 200: The member. Returns Collaborator.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "collaborator",
"id": "usr_3n7p1x",
"name": "Sam",
"email": "sam@example.com",
"role": "read_only",
"accepted_at": 1790800914012,
"has_mfa": false
}Remove a member, or leave the project#
DELETE /v2/projects/{project_id}/collaborators/{user_id} · Auth: dashboard session · RevenueDot extension
Any member can remove themselves. Removing someone else takes an admin. The last admin cannot leave or be removed (422): make someone else an admin first, or delete the project.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
user_id |
string | yes | The member's user id. Your own id leaves the project. |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/collaborators/$USER_ID"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "collaborator",
"id": "…",
"deleted_at": 1790801342625
}Project settings#
Project name, transfer behaviour, sandbox testing access, ownership and deletion. See Project settings.
Get a project with its settings#
GET /v2/projects/{project_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The project. Returns ProjectSettings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "project",
"id": "proj18pzzkao",
"name": "My app",
"created_at": 1790800900675,
"icon_url": null,
"icon_url_large": null,
"transfer_behavior": "transfer",
"sandbox_transfer_behavior": null,
"sandbox_testing_access": "anybody",
"sandbox_testers": [],
"owner": {
"id": "usr_8f2kq0x1m3zv7a2b",
"email": "founder@example.com",
"name": "Ana"
}
}Update a project's name, transfer behaviour and sandbox testing access#
POST /v2/projects/{project_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
See who owns a restored purchase and sandbox testing access. Duplicate and blank sandbox_testers are dropped.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
transfer_behavior |
transfer, transfer_if_no_active, keep, share |
no | |
sandbox_transfer_behavior |
transfer, transfer_if_no_active, keep, share, null |
no | |
sandbox_testing_access |
anybody, allowlist, nobody |
no | |
sandbox_testers |
array of string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"sandbox_testing_access":"allowlist","sandbox_testers":["qa_tester_1","qa_tester_2"]}'Responses
- 200: The project. Returns ProjectSettings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete a project and everything in it#
DELETE /v2/projects/{project_id} · Auth: dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Only a project admin signed in to the dashboard can do this. Apps, catalog, customers, purchases, events and webhooks are deleted. Cannot be undone.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "project",
"id": "…",
"deleted_at": 1790801342625
}Get the Customer Center configuration of the project#
GET /v2/projects/{project_id}/customer_center_config · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Three views of the configuration: customer_center is what the SDK receives (Retention offers resolved, editor-only fields removed); config is the editable document, the default with the stored overrides merged in (what the dashboard editor shows); overrides is what is stored (null when none).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
locale |
string | no | Build customer_center for this device locale (for example de_DE), as the SDK's X-Preferred-Locales header does. English when omitted. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customer_center_config" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The configuration.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Set the Customer Center configuration#
POST /v2/projects/{project_id}/customer_center_config · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Stores the configuration, merged key by key over the built-in default (lists such as paths replace). Send null to go back to the default. The SDK's shape plus editor fields: each screen's ordered paths (MISSING_PURCHASE, REFUND_REQUEST, CHANGE_PLANS, CANCEL, CUSTOM_URL with url and open_method, CUSTOM_ACTION with action_identifier); a feedback_survey on CANCEL; a promotional_offer on CANCEL, REFUND_REQUEST and survey options, either an offer of its own (title, subtitle, product_mapping) or { "retention_offer_id": "..." } pointing at a Retention offer, or null for no offer; appearance.light and .dark hex colours; title_localizations / subtitle_localizations per language; localization.custom_strings per language. The merged result is validated as a whole; a 400 names each field that is wrong. Custom URLs cannot use javascript:, data: or file:, and the document can be at most 1 MB.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
customer_center |
object or null | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customer_center_config" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"customer_center":{"support":{"email":"help@example.com"},"screens":{"MANAGEMENT":{"type":"MANAGEMENT","title":"How can we help?","paths":[{"id":"path_help","type":"CUSTOM_URL","title":"Help center","url":"https://example.com/help","open_method":"IN_APP"},{"id":"path_cancel","type":"CANCEL","title":"Cancel subscription","promotional_offer":{"retention_offer_id":"rto_123"}}]}},"appearance":{"light":{"accent_color":"#F4A900"}},"localization":{"custom_strings":{"de":{"contact_support":"Schreib uns"}}}}}'Responses
- 200: The saved configuration.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Transfer project ownership to an admin#
POST /v2/projects/{project_id}/actions/transfer_ownership · Auth: dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Only the owner (any admin when the owner has left the project) signed in to the dashboard. The new owner must already be a collaborator with the Admin role (422 otherwise). The previous owner stays an admin. Both get an email; email_sent is false when either could not be sent. Recorded in the audit log as project_transfer_ownership.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
string | yes | The new owner's user id, from the collaborators list. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/actions/transfer_ownership" \
-H "Content-Type: application/json" -d '{"user_id":"usr_8f2kq0x1m3zv7a2b"}'Responses
- 200: The project with its new owner.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Brand#
Colour and gradient presets for the paywall editor and the SDKs' named colours.
Colour and gradient presets#
GET /v2/projects/{project_id}/brand · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/brand" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The presets.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "brand",
"color_presets": [
{
"key": "ink",
"name": "Ink",
"light": "#0a0a0aff",
"dark": "#fafafaff"
},
{
"key": "gold",
"name": "Brand gold",
"light": "#f7b500ff",
"dark": null
}
],
"gradient_presets": [
{
"key": "sunrise",
"name": "Sunrise",
"type": "linear",
"degrees": 135,
"points": [
{
"color": "#f7b500ff",
"percent": 0
},
{
"color": "#c2410cff",
"percent": 100
}
],
"dark_points": null
}
]
}Replace colour or gradient presets#
POST /v2/projects/{project_id}/brand · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Send a whole list to replace it; a list you leave out is kept. The paywall editor shows the presets in its colour pickers, and the SDKs receive every preset as a named colour in the offerings' ui_config.app.colors (a paywall colour {"type": "alias", "value": "<key>"} uses it). A deleted preset that a paywall names this way draws nothing in the SDK, so presets picked in the editor are copied as values.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
color_presets |
array of object | no | |
color_presets[].key |
string | yes | a-z, 0-9 and _; the name in ui_config.app.colors. Unique across colour and gradient presets. |
color_presets[].name |
string | yes | |
color_presets[].light |
string | yes | #RRGGBB or #RRGGBBAA; answers are lower case with alpha. |
color_presets[].dark |
string or null | no | Null: the light colour in dark mode too. |
gradient_presets |
array of object | no | |
gradient_presets[].key |
string | yes | Unique across colour and gradient presets. |
gradient_presets[].name |
string | yes | |
gradient_presets[].type |
linear, radial |
yes | |
gradient_presets[].degrees |
integer | no | Linear only, 0 to 360. Default 180. |
gradient_presets[].points |
array of object | yes | |
gradient_presets[].points[].color |
string | yes | #RRGGBB or #RRGGBBAA; answers are lower case with alpha. |
gradient_presets[].points[].percent |
integer | yes | 0 to 100. |
gradient_presets[].dark_points |
array of object | no | Null: the light stops in dark mode too. |
gradient_presets[].dark_points[].color |
string | yes | #RRGGBB or #RRGGBBAA; answers are lower case with alpha. |
gradient_presets[].dark_points[].percent |
integer | yes | 0 to 100. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/brand" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"color_presets":[{"key":"ink","name":"Ink","light":"#0A0A0A","dark":"#FAFAFA"}]}'Responses
- 200: The presets.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "brand",
"color_presets": [
{
"key": "ink",
"name": "Ink",
"light": "#0a0a0aff",
"dark": "#fafafaff"
},
{
"key": "gold",
"name": "Brand gold",
"light": "#f7b500ff",
"dark": null
}
],
"gradient_presets": [
{
"key": "sunrise",
"name": "Sunrise",
"type": "linear",
"degrees": 135,
"points": [
{
"color": "#f7b500ff",
"percent": 0
},
{
"color": "#c2410cff",
"percent": 100
}
],
"dark_points": null
}
]
}Blocked customers#
App user ids that lose access to paid features on every platform.
List blocked app user ids#
GET /v2/projects/{project_id}/blocked_customers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Newest first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
search |
string | no | Part of an app user id. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/blocked_customers" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Blocked app user ids.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "blocked_customer",
"id": "user_4471",
"app_user_id": "user_4471",
"note": "Chargeback abuse",
"blocked_at": 1790894800000,
"blocked_by": {
"type": "user",
"id": "usr_8f2kq0x1m3zv7a2b",
"email": "support@example.com"
},
"customer_exists": true
}
],
"next_page": null,
"url": "/v2/projects/proj1a2b3c4d/blocked_customers"
}Block an app user id#
POST /v2/projects/{project_id}/blocked_customers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read_write
The customer with this app user id (any of its aliases) loses access to paid features on every platform: customer info has no entitlements, API v2 active_entitlements is empty, targeting sees none, and purchases credit no in-app currency. Purchases are still recorded and webhooks are still sent, with their usual entitlement_ids; if you grant access from webhooks, check this list. The id need not exist yet. 201 when blocked now, 200 when it already was.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_user_id |
string | yes | |
note |
string or null | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/blocked_customers" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"app_user_id":"user_4471","note":"Chargeback abuse"}'Responses
- 200: Already blocked.
- 201: Blocked.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "blocked_customer",
"id": "user_4471",
"app_user_id": "user_4471",
"note": "Chargeback abuse",
"blocked_at": 1790894800000,
"blocked_by": {
"type": "user",
"id": "usr_8f2kq0x1m3zv7a2b",
"email": "support@example.com"
},
"customer_exists": true
}Is this app user id blocked?#
GET /v2/projects/{project_id}/blocked_customers/{app_user_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_user_id |
string | yes | URL-encoded. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/blocked_customers/user_1" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Blocked.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "blocked_customer",
"id": "user_4471",
"app_user_id": "user_4471",
"note": "Chargeback abuse",
"blocked_at": 1790894800000,
"blocked_by": {
"type": "user",
"id": "usr_8f2kq0x1m3zv7a2b",
"email": "support@example.com"
},
"customer_exists": true
}Unblock an app user id#
DELETE /v2/projects/{project_id}/blocked_customers/{app_user_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read_write
Access comes back at once.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_user_id |
string | yes | URL-encoded. |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/blocked_customers/user_1" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Unblocked.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Verified Metrics#
A public page with a project's aggregate production numbers, its settings, image and JSON.
Verified Metrics page settings#
GET /v2/projects/{project_id}/verified_metrics · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
A project that never saved the page gets a draft: a free slug from the project name, all six metrics shown.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The settings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "verified_metrics",
"status": "published",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"metrics": [
{
"id": "mrr",
"visible": true
},
{
"id": "revenue",
"visible": true
},
{
"id": "active_subscriptions",
"visible": true
},
{
"id": "active_trials",
"visible": true
},
{
"id": "new_customers",
"visible": false
},
{
"id": "active_users",
"visible": false
}
],
"show_icon": false,
"icon_asset_id": null,
"show_store_links": true,
"app_store_url": "https://apps.apple.com/app/id1234567890",
"play_store_url": null,
"url": "https://api.revenuedot.app/verified/scanner",
"published_at": 1790894800000,
"updated_at": 1790894800000,
"custom_domain": null
}Save Verified Metrics page settings#
POST /v2/projects/{project_id}/verified_metrics · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
A published page shows the change within 15 minutes (the cached copies are dropped on Cloud at once). 409 when another project uses the slug.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | no | 3 to 40 characters: a-z, 0-9 and single dashes, not at either end. Unique on the server; a few words such as admin are reserved. |
display_name |
string | no | |
chart_type |
number_sparkline, numbers_only, line |
no | number_sparkline (Number & sparklines, the default), numbers_only (Only numbers) or line (Line charts: one point per month over the last 12 calendar months, RevenueDot's own). |
metrics |
array of object | no | The 6 overview metrics in display order, each once. |
metrics[].id |
mrr, revenue, active_subscriptions, active_trials, new_customers, active_users |
yes | |
metrics[].visible |
boolean | yes | |
show_icon |
boolean | no | |
icon_asset_id |
string or null | no | An image uploaded with POST /v2/projects/{project_id}/media_assets. |
show_store_links |
boolean | no | |
app_store_url |
string or null | no | https on apps.apple.com. |
play_store_url |
string or null | no | https on play.google.com. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"display_name":"Scanner Pro","show_store_links":true,"app_store_url":"https://apps.apple.com/app/id1234567890"}'Responses
- 200: The settings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "verified_metrics",
"status": "published",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"metrics": [
{
"id": "mrr",
"visible": true
},
{
"id": "revenue",
"visible": true
},
{
"id": "active_subscriptions",
"visible": true
},
{
"id": "active_trials",
"visible": true
},
{
"id": "new_customers",
"visible": false
},
{
"id": "active_users",
"visible": false
}
],
"show_icon": false,
"icon_asset_id": null,
"show_store_links": true,
"app_store_url": "https://apps.apple.com/app/id1234567890",
"play_store_url": null,
"url": "https://api.revenuedot.app/verified/scanner",
"published_at": 1790894800000,
"updated_at": 1790894800000,
"custom_domain": null
}Whether a slug is free#
GET /v2/projects/{project_id}/verified_metrics/slug_availability · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics/slug_availability" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The answer.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "slug_availability",
"slug": "scanner",
"available": true,
"reason": null
}The 12 monthly points of each metric#
GET /v2/projects/{project_id}/verified_metrics/monthly_history · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
What the Line charts type draws, production only: MRR, active subscriptions and active trials at each month's end (the current month live), revenue and new customers as monthly totals; active_users is null (no monthly history).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics/monthly_history" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The points, oldest first.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "verified_metrics_monthly_history",
"months": 12,
"metrics": {
"mrr": [
{
"date": "2025-11",
"value": 1879.19
}
],
"active_users": null
}
}Set or clear the page's custom domain#
PUT /v2/projects/{project_id}/verified_metrics/domain · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
A subdomain you own, such as metrics.yourapp.com; null removes it. The answer's custom_domain.dns lists the CNAME and TXT records to add. Changing or removing the domain resets its proof. 409 when another project's page, or hosted web pages, use the domain.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
custom_domain |
string or null | yes |
Example request
curl -s -X PUT "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics/domain" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"custom_domain":"metrics.yourapp.com"}'Responses
- 200: The settings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "verified_metrics",
"status": "published",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"metrics": [
{
"id": "mrr",
"visible": true
},
{
"id": "revenue",
"visible": true
},
{
"id": "active_subscriptions",
"visible": true
},
{
"id": "active_trials",
"visible": true
},
{
"id": "new_customers",
"visible": false
},
{
"id": "active_users",
"visible": false
}
],
"show_icon": false,
"icon_asset_id": null,
"show_store_links": true,
"app_store_url": "https://apps.apple.com/app/id1234567890",
"play_store_url": null,
"url": "https://api.revenuedot.app/verified/scanner",
"published_at": 1790894800000,
"updated_at": 1790894800000,
"custom_domain": {
"domain": "metrics.yourapp.com",
"status": "pending",
"url": null,
"verified_at": null,
"checked_at": null,
"error": null,
"dns": [
{
"type": "CNAME",
"name": "metrics.yourapp.com",
"value": "api.revenuedot.app"
},
{
"type": "TXT",
"name": "_revenuedot.metrics.yourapp.com",
"value": "revenuedot-verify=3f9c2a"
}
],
"certificate": {
"managed": "manual",
"status": null,
"note": "RevenueDot adds the TLS certificate for your domain after it is verified, usually within one business day. Until then the page stays on its RevenueDot URL."
}
}
}Check the custom domain's DNS#
POST /v2/projects/{project_id}/verified_metrics/domain/actions/verify · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Reads the TXT and CNAME records over DNS over HTTPS, 6 checks a minute. Once verified and published, the domain serves the page at /, /metrics.json, /og.png and /icon, and nothing else. On RevenueDot Cloud the answer also reports the TLS certificate. found holds the records seen.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics/domain/actions/verify" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The settings and the records found.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
Example 200 response:
{
"object": "verified_metrics",
"status": "published",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"metrics": [
{
"id": "mrr",
"visible": true
},
{
"id": "revenue",
"visible": true
},
{
"id": "active_subscriptions",
"visible": true
},
{
"id": "active_trials",
"visible": true
},
{
"id": "new_customers",
"visible": false
},
{
"id": "active_users",
"visible": false
}
],
"show_icon": false,
"icon_asset_id": null,
"show_store_links": true,
"app_store_url": "https://apps.apple.com/app/id1234567890",
"play_store_url": null,
"url": "https://api.revenuedot.app/verified/scanner",
"published_at": 1790894800000,
"updated_at": 1790894800000,
"custom_domain": {
"domain": "metrics.yourapp.com",
"status": "verified",
"url": "https://metrics.yourapp.com",
"verified_at": 1790894800000,
"checked_at": 1790894800000,
"error": null,
"dns": [
{
"type": "CNAME",
"name": "metrics.yourapp.com",
"value": "api.revenuedot.app"
},
{
"type": "TXT",
"name": "_revenuedot.metrics.yourapp.com",
"value": "revenuedot-verify=3f9c2a"
}
],
"certificate": {
"managed": "manual",
"status": null,
"note": "RevenueDot adds the TLS certificate for your domain after it is verified, usually within one business day. Until then the page stays on its RevenueDot URL."
}
},
"found": {
"cname": [
"api.revenuedot.app"
],
"txt": [
"revenuedot-verify=3f9c2a"
]
}
}Publish the page#
POST /v2/projects/{project_id}/verified_metrics/actions/publish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Takes the same fields as the update, saves them and publishes. The page then answers at /verified/{slug}.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | no | 3 to 40 characters: a-z, 0-9 and single dashes, not at either end. Unique on the server; a few words such as admin are reserved. |
display_name |
string | no | |
chart_type |
number_sparkline, numbers_only, line |
no | number_sparkline (Number & sparklines, the default), numbers_only (Only numbers) or line (Line charts: one point per month over the last 12 calendar months, RevenueDot's own). |
metrics |
array of object | no | The 6 overview metrics in display order, each once. |
metrics[].id |
mrr, revenue, active_subscriptions, active_trials, new_customers, active_users |
yes | |
metrics[].visible |
boolean | yes | |
show_icon |
boolean | no | |
icon_asset_id |
string or null | no | An image uploaded with POST /v2/projects/{project_id}/media_assets. |
show_store_links |
boolean | no | |
app_store_url |
string or null | no | https on apps.apple.com. |
play_store_url |
string or null | no | https on play.google.com. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics/actions/publish" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"slug":"scanner"}'Responses
- 200: The settings, published.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "verified_metrics",
"status": "published",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"metrics": [
{
"id": "mrr",
"visible": true
},
{
"id": "revenue",
"visible": true
},
{
"id": "active_subscriptions",
"visible": true
},
{
"id": "active_trials",
"visible": true
},
{
"id": "new_customers",
"visible": false
},
{
"id": "active_users",
"visible": false
}
],
"show_icon": false,
"icon_asset_id": null,
"show_store_links": true,
"app_store_url": "https://apps.apple.com/app/id1234567890",
"play_store_url": null,
"url": "https://api.revenuedot.app/verified/scanner",
"published_at": 1790894800000,
"updated_at": 1790894800000,
"custom_domain": null
}Unpublish the page#
POST /v2/projects/{project_id}/verified_metrics/actions/unpublish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
The page answers 404 at once; the status becomes inactive. 422 when it is not published.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/verified_metrics/actions/unpublish" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The settings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "verified_metrics",
"status": "inactive",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"metrics": [
{
"id": "mrr",
"visible": true
},
{
"id": "revenue",
"visible": true
},
{
"id": "active_subscriptions",
"visible": true
},
{
"id": "active_trials",
"visible": true
},
{
"id": "new_customers",
"visible": false
},
{
"id": "active_users",
"visible": false
}
],
"show_icon": false,
"icon_asset_id": null,
"show_store_links": true,
"app_store_url": "https://apps.apple.com/app/id1234567890",
"play_store_url": null,
"url": "https://api.revenuedot.app/verified/scanner",
"published_at": 1790894800000,
"updated_at": 1790894800000,
"custom_domain": null
}The public Verified Metrics page#
GET /verified/{slug} · Auth: none · RevenueDot extension
HTML with no scripts: the display name, the visible metrics with 28-day sparklines, optional icon and store links, and link-preview tags pointing at og.png. Only aggregate production numbers: no customers, no sandbox data, no project id. Cache-Control: public, max-age=300, s-maxage=900, an ETag (304 on If-None-Match), and on Cloud a copy in the edge cache keyed by the page's version, so a saved page shows its changes at once. The slug is case-insensitive. 404 with no-store when the slug is unknown or not published.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string | yes | The page's slug. |
Example request
curl -s "$REVENUEDOT_URL/verified/$SLUG"Responses
- 200: The page.
- 404: Not published.
The page's numbers as JSON#
GET /verified/{slug}/metrics.json · Auth: none · RevenueDot extension
Same caching as the page. computed_at is when the numbers were computed. icon_url is /verified/{slug}/icon?v=<asset id> when the page shows an icon.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string | yes | The page's slug. |
Example request
curl -s "$REVENUEDOT_URL/verified/$SLUG/metrics.json"Responses
- 200: The numbers.
- 404: Not published.
Example 200 response:
{
"object": "verified_metrics_page",
"url": "https://api.revenuedot.app/verified/scanner",
"slug": "scanner",
"display_name": "Scanner",
"chart_type": "number_sparkline",
"computed_at": 1790894800000,
"icon_url": null,
"store_links": {
"app_store": "https://apps.apple.com/app/id1234567890",
"play_store": null
},
"metrics": [
{
"id": "mrr",
"name": "MRR",
"unit": "$",
"caption": "Monthly recurring revenue",
"value": 93.4,
"sparkline": [
88.1,
90.2,
93.4
]
}
]
}The page's project icon#
GET /verified/{slug}/icon · Auth: none · RevenueDot extension
The uploaded image the page shows as its icon, served by slug so the page never names the project. Same caching as the page; 404 when the page is not published or shows no icon.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string | yes | The page's slug. |
Example request
curl -s "$REVENUEDOT_URL/verified/$SLUG/icon"Responses
- 200: The image (PNG, JPEG or WebP).
- 404: Not published, or no icon.
The page's 1200×630 link preview#
GET /verified/{slug}/og.png · Auth: none · RevenueDot extension
A PNG drawn on the server: the display name and up to three visible metrics with sparklines.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string | yes | The page's slug. |
Example request
curl -s "$REVENUEDOT_URL/verified/$SLUG/og.png"Responses
- 200: The image.
- 404: Not published.
Auth#
Identity providers for Auth (Firebase, OpenID Connect), the project switch, a token tester, and signed-in identities with their balances for your backend. See Auth.
Auth on or off#
GET /v2/projects/{project_id}/auth/settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/settings" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The settings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "auth_settings",
"enabled": true,
"allow_anonymous": false
}Turn Auth or anonymous sign-in on or off#
POST /v2/projects/{project_id}/auth/settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | no | |
allow_anonymous |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/settings" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"enabled":true}'Responses
- 200: The settings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
List identity providers#
GET /v2/projects/{project_id}/auth/providers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/providers" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Providers.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "auth_provider",
"id": "idp_4f1c9a2b7e3d",
"kind": "firebase",
"name": "Firebase (scanner-1a2b3)",
"issuer": "https://securetoken.google.com/scanner-1a2b3",
"audiences": [
"scanner-1a2b3"
],
"firebase_project_id": "scanner-1a2b3",
"jwks_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com",
"jwks_source": "firebase",
"app_user_id_claim": "sub",
"app_user_id_prefix": "",
"enabled": true,
"created_at": 1790894800000,
"updated_at": 1790894800000
}
],
"next_page": null,
"url": "/v2/projects/proj1a2b3c4d/auth/providers"
}Add a Firebase or OpenID Connect provider#
POST /v2/projects/{project_id}/auth/providers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Up to 10 per project. Google, Apple, Auth0, Clerk, Supabase and Cognito are OpenID Connect providers: Google's issuer is https://accounts.google.com, Apple's https://appleid.apple.com.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
kind |
firebase, oidc |
yes | |
name |
string | no | |
firebase_project_id |
string | no | Firebase only. Tokens must have iss https://securetoken.google.com/aud |
issuer |
string | no | OpenID Connect only: exactly the tokens' iss. |
audiences |
array of string | no | OpenID Connect only: client ids a token's aud must name one of. |
jwks_url |
string or null | no | OpenID Connect only. Null: jwks_uri from <issuer>/.well-known/openid-configuration. On Cloud it must be a public https URL. |
app_user_id_claim |
string | no | The claim that becomes the app user id. Default sub. |
app_user_id_prefix |
string | no | Put in front of the claim, such as firebase:. Default empty. |
enabled |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/providers" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"kind":"oidc","issuer":"https://scanner.eu.auth0.com/","audiences":["sBk2x9Lq0aZ"],"app_user_id_prefix":"auth0:"}'Responses
- 201: The provider.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 201 response:
{
"object": "auth_provider",
"id": "idp_4f1c9a2b7e3d",
"kind": "firebase",
"name": "Firebase (scanner-1a2b3)",
"issuer": "https://securetoken.google.com/scanner-1a2b3",
"audiences": [
"scanner-1a2b3"
],
"firebase_project_id": "scanner-1a2b3",
"jwks_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com",
"jwks_source": "firebase",
"app_user_id_claim": "sub",
"app_user_id_prefix": "",
"enabled": true,
"created_at": 1790894800000,
"updated_at": 1790894800000
}Get an identity provider#
GET /v2/projects/{project_id}/auth/providers/{provider_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
provider_id |
string | yes | Starts with idp. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/providers/$PROVIDER_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The provider.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "auth_provider",
"id": "idp_4f1c9a2b7e3d",
"kind": "firebase",
"name": "Firebase (scanner-1a2b3)",
"issuer": "https://securetoken.google.com/scanner-1a2b3",
"audiences": [
"scanner-1a2b3"
],
"firebase_project_id": "scanner-1a2b3",
"jwks_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com",
"jwks_source": "firebase",
"app_user_id_claim": "sub",
"app_user_id_prefix": "",
"enabled": true,
"created_at": 1790894800000,
"updated_at": 1790894800000
}Update or turn off an identity provider#
POST /v2/projects/{project_id}/auth/providers/{provider_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Send only what changes. People who already signed in keep their app user id when the mapping changes.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
provider_id |
string | yes | Starts with idp. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
firebase_project_id |
string | no | Firebase only. Tokens must have iss https://securetoken.google.com/aud |
issuer |
string | no | OpenID Connect only: exactly the tokens' iss. |
audiences |
array of string | no | OpenID Connect only: client ids a token's aud must name one of. |
jwks_url |
string or null | no | OpenID Connect only. Null: jwks_uri from <issuer>/.well-known/openid-configuration. On Cloud it must be a public https URL. |
app_user_id_claim |
string | no | The claim that becomes the app user id. Default sub. |
app_user_id_prefix |
string | no | Put in front of the claim, such as firebase:. Default empty. |
enabled |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/providers/$PROVIDER_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"enabled":false}'Responses
- 200: The provider.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete an identity provider#
DELETE /v2/projects/{project_id}/auth/providers/{provider_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Ends every session it signed in and removes its identity links. App user ids, purchases and balances stay.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
provider_id |
string | yes | Starts with idp. |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/providers/$PROVIDER_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Check an ID token without signing in#
POST /v2/projects/{project_id}/auth/providers/{provider_id}/actions/test · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Runs every check of a sign-in and reports the app user id it would sign in as. Nothing is stored. A token that fails answers 200 with valid: false and the reason.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
provider_id |
string | yes | Starts with idp. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
id_token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/providers/$PROVIDER_ID/actions/test" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6…"}'Responses
- 200: The result.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "auth_token_test",
"valid": true,
"subject": "Xk2f9QpL0aZ",
"app_user_id": "Xk2f9QpL0aZ",
"linked": false,
"claims": {
"iss": "https://securetoken.google.com/scanner-1a2b3",
"aud": "scanner-1a2b3",
"sub": "Xk2f9QpL0aZ",
"exp": 1790898400
},
"error": null
}List signed-in identities#
GET /v2/projects/{project_id}/auth/identities · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Most recent sign-in first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
provider_id |
string | no | |
subject |
string | no | |
app_user_id |
string | no | |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/identities" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Identities.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "auth_identity",
"provider_id": "idp_4f1c9a2b7e3d",
"subject": "Xk2f9QpL0aZ",
"app_user_id": "Xk2f9QpL0aZ",
"logins": 12,
"last_login_at": 1790894800000,
"created_at": 1790800000000
}
],
"next_page": null,
"url": "/v2/projects/proj1a2b3c4d/auth/identities"
}Read a signed-in user's balances and entitlements by identity#
GET /v2/projects/{project_id}/auth/identities/{provider_id}/{subject} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
For your backend: look up a user by the provider's user id (a Firebase uid, an Auth0 sub) and read their in-app currency balances and active entitlements, without knowing the app user id.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
provider_id |
string | yes | Starts with idp. |
subject |
string | yes | The provider's user id (sub), URL-encoded. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/identities/$PROVIDER_ID/$SUBJECT" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The identity.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "auth_identity",
"provider_id": "idp_4f1c9a2b7e3d",
"subject": "Xk2f9QpL0aZ",
"app_user_id": "Xk2f9QpL0aZ",
"logins": 12,
"last_login_at": 1790894800000,
"created_at": 1790800000000,
"customer_id": "Xk2f9QpL0aZ",
"active_entitlements": [
{
"lookup_key": "pro",
"expires_at": 1793486800000
}
],
"virtual_currencies": {
"GEMS": {
"balance": 40,
"name": "Gems",
"code": "GEMS"
}
}
}Unlink an identity and sign it out#
DELETE /v2/projects/{project_id}/auth/identities/{provider_id}/{subject} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read_write
Ends its sessions at once. The next sign-in maps the identity again with the provider's current mapping.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
provider_id |
string | yes | Starts with idp. |
subject |
string | yes | URL-encoded. |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/auth/identities/$PROVIDER_ID/$SUBJECT" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Unlinked.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Store setup#
Notification URLs, credential checks, setup health and App Store mass extensions.
Store setup state of an app#
GET /v2/projects/{project_id}/apps/{app_id}/store_settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
The notification URL to paste into App Store Connect, Pub/Sub, the Amazon Appstore Console, Stripe, Paddle, the Roku developer dashboard or Samsung Seller Portal, the notification status, the forwarding URL and which credentials are set. Never a secret.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/store_settings" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The settings. Returns StoreSettings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Download the sample app for this app#
GET /v2/projects/{project_id}/apps/{app_id}/sample_app · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
"Test your setup with the sample app": a zip of the matching example from revenuedot/examples with this app's public key, this server's URL (api_origin; localhost becomes 10.0.2.2 for the Android emulator) and the project's first entitlement filled in. Flutter, React Native and web samples get a .env. Only public values go in. The X-RevenueDot-Examples-Commit header names the examples commit.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
platform |
ios, android, flutter, react_native, web |
no | ios, android, flutter, react_native or web; the app's sample_apps in store settings lists the ones offered (Test Store: all five; App Store: iOS, Flutter, React Native; Google Play: Android, Flutter, React Native; Web Billing: web). Defaults to the first. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/sample_app" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The zip.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Check store credentials with the store#
POST /v2/projects/{project_id}/apps/{app_id}/actions/verify_credentials · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
Makes one harmless call to the App Store Server API, the Play Developer API, Amazon's Receipt Verification Service (a made-up receipt: 496 means a wrong shared key), Stripe (lists one subscription and one Checkout Session with the key), Paddle (event types, then one product and one subscription; Paddle answers 403 for a wrong, revoked or other-environment key), Roku Pay (a made-up transaction: UNAUTHORIZED means a wrong key) or Samsung (an access token from the service account, then a made-up subscription). Values in the body are checked before you save them; missing values fall back to the saved ones. A Stripe app connected with Stripe Connect checks its connection only: Stripe values in the body answer 409.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_store |
object | no | |
app_store.bundle_id |
string or null | no | |
app_store.subscription_private_key |
string or null | no | |
app_store.subscription_key_id |
string or null | no | |
app_store.subscription_key_issuer |
string or null | no | |
mac_app_store |
object | no | |
mac_app_store.bundle_id |
string or null | no | |
mac_app_store.subscription_private_key |
string or null | no | |
mac_app_store.subscription_key_id |
string or null | no | |
mac_app_store.subscription_key_issuer |
string or null | no | |
play_store |
object | no | |
play_store.package_name |
string or null | no | |
play_store.play_service_account_credentials_json |
string or object or null | no | |
amazon |
object | no | |
amazon.package_name |
string or null | no | |
amazon.shared_secret |
string or null | no | |
stripe |
object | no | |
stripe.stripe_secret_key |
string or null | no | |
stripe.stripe_account_id |
string or null | no | |
paddle |
object | no | |
paddle.paddle_api_key |
string or null | no | |
paddle.paddle_is_sandbox |
boolean or null | no | |
roku |
object | no | |
roku.roku_api_key |
string or null | no | |
galaxy |
object | no | |
galaxy.package_name |
string or null | no | |
galaxy.galaxy_service_account_id |
string or null | no | |
galaxy.galaxy_service_account_private_key |
string or null | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/actions/verify_credentials" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{}'Responses
- 200: The result. Returns CredentialsCheck.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "credentials_check",
"app_id": "appugfw01uy",
"store": "app_store",
"status": "invalid",
"valid": false,
"message": "No in-app purchase key yet. Add the .p8 file, the key ID and the issuer ID.",
"checked_at": 1790801342700
}Check the App Store Connect API key with Apple#
POST /v2/projects/{project_id}/apps/{app_id}/actions/verify_app_store_connect_key · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Checks the App Store Connect API key that Import products and the product editor use: it signs a token, finds the app by its bundle ID and reads its products. Read-only at Apple; the result is not stored. Values in the body are checked before you save them; missing values fall back to the saved ones. Only for App Store and Mac App Store apps (400 otherwise). Needs write access because the saved key with another bundle ID could describe any app of the developer's Apple team.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
bundle_id |
string or null | no | |
app_store_connect_api_key |
string or null | no | The .p8 file's contents. |
app_store_connect_api_key_id |
string or null | no | |
app_store_connect_api_key_issuer |
string or null | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/actions/verify_app_store_connect_key" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{}'Responses
- 200: The result. Returns CredentialsCheck.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "credentials_check",
"app_id": "appugfw01uy",
"store": "app_store",
"key": "app_store_connect_api_key",
"status": "valid",
"valid": true,
"message": "Key 2X9R4HXF34 works: it sees Scanner (Apple ID 6478123456) and can read its 1 subscription group and 3 in-app purchases.",
"checked_at": 1790801342700,
"app_store_app_id": "6478123456",
"app_name": "Scanner",
"subscription_groups": 1,
"in_app_purchases": 3
}Paddle: create the notification destination (Apply in Paddle)#
POST /v2/projects/{project_id}/apps/{app_id}/actions/apply_notification_settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Creates a notification destination in the app's Paddle account (or updates the one it created before) that sends the subscription, transaction and adjustment events RevenueDot reads to this app's notification URL, and saves the destination's secret key, sealed. The API key needs write access to Notification settings. Only for Paddle apps (422 otherwise). See Paddle setup.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/actions/apply_notification_settings" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The destination.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "notification_settings",
"app_id": "app4f1x9k2m",
"store": "paddle",
"notification_setting_id": "ntfset_01h8d3a0kq7m2x9c4v6b1n5p3r",
"destination": "https://api.revenuedot.app/v1/notifications/paddle/app4f1x9k2m",
"subscribed_events": [
"subscription.created",
"subscription.updated",
"transaction.completed",
"adjustment.created"
],
"secret_saved": true
}Extend every active App Store subscriber of a product#
POST /v2/projects/{project_id}/apps/{app_id}/actions/mass_extend · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:subscriptions:read_write
Asks Apple to extend renewal dates for all active subscribers of product_id. Apple then sends one notification per subscription, which records SUBSCRIPTION_EXTENDED.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
product_id |
string | yes | |
extend_by_days |
integer | yes | |
extend_reason_code |
undeclared, customer_satisfaction, other, service_issue_or_outage |
yes | |
storefront_country_codes |
array of string | no | |
environment |
production, sandbox |
no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/actions/mass_extend" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"product_id":"pro_monthly","extend_by_days":3,"extend_reason_code":"service_issue_or_outage"}'Responses
- 202: Accepted by Apple. Returns MassExtension.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Status of a mass extension#
GET /v2/projects/{project_id}/apps/{app_id}/mass_extensions/{request_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:subscriptions:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
request_id |
string | yes | The id from the mass extend answer. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
product_id |
string | yes | |
environment |
production, sandbox |
no |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/mass_extensions/$REQUEST_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The status. Returns MassExtension.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Setup health#
GET /v2/projects/{project_id}/setup_health · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
Per app: the notification URL and whether notifications arrive. For webhooks: deliveries in the last 24 hours and failing endpoints. Also the SDK versions calling the server.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/setup_health" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Setup health. Returns SetupHealth.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Store prices and product editor#
Prices in every territory, periods and store states read from App Store Connect and Google Play, and the product editor: download a CSV of prices per territory, upload it after changing prices or adding products, review the check, and commit it to the store. See Product editor.
List store prices#
GET /v2/projects/{project_id}/store_prices · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read
Every App Store, Mac App Store and Google Play product RevenueDot has read for the project's apps, with its price in every territory, its period and the store's state. The answer comes from RevenueDot's copy of the last read; the store is not asked. apps has one item per App Store, Mac App Store and Google Play app: whether RevenueDot can read its prices (and why not), and the last read's outcome. next_page is always null.
Prices are read again on POST …/apps/{app_id}/store_prices/actions/refresh, on every product file download and upload, after every commit, and once a day for apps not read in 24 hours. See Product editor.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
app_id |
string | no | Only this app's store products. 404 when the app is not in the project. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/store_prices" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The cached store products and each app's last read. Returns a list of StoreListing.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "store_listing",
"app_id": "app1a2b3c4d",
"store_identifier": "pro_monthly",
"product_id": "prod1a2b3c4d5e",
"type": "subscription",
"display_name": "Pro Monthly",
"duration": "P1M",
"store_state": "APPROVED",
"status": "approved",
"group": {
"id": "21000001",
"name": "Pro"
},
"store_id": "6743920115",
"price": {
"amount_micros": 9990000,
"currency": "USD",
"territory": "USA"
},
"prices": [
{
"territory": "DEU",
"currency": "EUR",
"amount_micros": 9990000
},
{
"territory": "GBR",
"currency": "GBP",
"amount_micros": 8990000
},
{
"territory": "USA",
"currency": "USD",
"amount_micros": 9990000
}
],
"editable": true,
"note": null,
"refreshed_at": 1790971200000
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/store_prices",
"apps": [
{
"object": "store_price_sync",
"app_id": "app1a2b3c4d",
"store": "app_store",
"can_read_prices": true,
"reason": null,
"status": "ok",
"error": null,
"item_count": 3,
"refreshed_at": 1790971200000
}
]
}Read an app's store prices again#
POST /v2/projects/{project_id}/apps/{app_id}/store_prices/actions/refresh · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
Reads every product of the app from the store with its current price in every territory, its period and its state, and replaces RevenueDot's copy for the app, so products deleted in the store disappear. Nothing in the store changes, and the refresh is not written to the audit log.
- App Store and Mac App Store need the App Store Connect API key (
app_store_connect_api_key,_id,_issuer): a team key with the App Manager role. Each subscription's prices (GET /v1/subscriptions/{id}/prices, the current price per territory), each in-app purchase's price schedule (manual prices and the ones Apple sets from the base territory), and the review state. - Google Play needs the service account, with "View app information and download bulk reports (read-only)". Every base plan's regional prices and state, and one-time products' prices.
When the read fails, the app's sync turns failing with the error and the previous prices stay. 422 unprocessable_entity_error for another kind of app or a missing or refused key; 422 store_error with retryable: true while the store cannot be reached.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/store_prices/actions/refresh" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: What the store has now. Returns StorePriceRefresh.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "store_price_refresh",
"app_id": "app1a2b3c4d",
"warnings": [],
"sync": {
"object": "store_price_sync",
"app_id": "app1a2b3c4d",
"store": "app_store",
"can_read_prices": true,
"reason": null,
"status": "ok",
"error": null,
"item_count": 3,
"refreshed_at": 1790971200000
},
"items": [
{
"object": "store_listing",
"app_id": "app1a2b3c4d",
"store_identifier": "pro_monthly",
"product_id": "prod1a2b3c4d5e",
"type": "subscription",
"display_name": "Pro Monthly",
"duration": "P1M",
"store_state": "APPROVED",
"status": "approved",
"group": {
"id": "21000001",
"name": "Pro"
},
"store_id": "6743920115",
"price": {
"amount_micros": 9990000,
"currency": "USD",
"territory": "USA"
},
"prices": [
{
"territory": "DEU",
"currency": "EUR",
"amount_micros": 9990000
},
{
"territory": "GBR",
"currency": "GBP",
"amount_micros": 8990000
},
{
"territory": "USA",
"currency": "USD",
"amount_micros": 9990000
}
],
"editable": true,
"note": null,
"refreshed_at": 1790971200000
}
]
}Download a product file#
GET /v2/projects/{project_id}/apps/{app_id}/store_products/export.csv · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read
A CSV with one row per product and territory: the base territory first (the United States when the product has a price there), then the rest by code. The columns are store_identifier, display_name, type, duration, group, territory, currency, price, action. price is a decimal in the territory's currency (9.99, 1740 for yen) and action is empty. A product without a price gets one row with empty territory, currency and price. Text cells that start with =, +, - or @ get a leading apostrophe so spreadsheets do not run them as formulas; the upload removes it.
The download reads the store again (like a refresh), so the file has today's prices. The Content-Disposition file name is <app>-app-store-products-<date>.csv or <app>-play-store-products-<date>.csv, and X-Product-Count is the number of products. See the CSV format.
store_identifier,display_name,type,duration,group,territory,currency,price,action
pro_monthly,Pro Monthly,subscription,P1M,Pro,USA,USD,9.99,
pro_monthly,Pro Monthly,subscription,P1M,Pro,DEU,EUR,9.99,
pro_monthly,Pro Monthly,subscription,P1M,Pro,GBR,GBP,8.99,400 without store_identifiers or all=true, or with more than 2,000 identifiers. 422 store_error (param: store_identifiers) for identifiers the store does not have for the app, and for Google Play one-time products, which the product editor does not change yet. Other 422s as for a refresh.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
store_identifiers |
string | no | The products to include, comma separated (pro_monthly,pro_annual); the parameter may repeat. At most 2,000. |
all |
boolean | no | true includes every product the product editor can change. Use it instead of store_identifiers. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/store_products/export.csv" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The CSV file.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
List product files#
GET /v2/projects/{project_id}/product_edits · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read
Every uploaded product file of the project, newest first (the last 200), with its status, counts and who uploaded it. The dashboard's Files tab. rows are left out; get one file for them. next_page is always null.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
app_id |
string | no | Only this app's files. 404 when the app is not in the project. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The files. Returns a list of ProductEdit.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "product_edit",
"id": "pedit3k9x2m7q1z8w4c",
"app_id": "app1a2b3c4d",
"store": "app_store",
"status": "partially_committed",
"file_name": "scanner-ios-app-store-products-2026-10-02.csv",
"created_at": 1790971500000,
"updated_at": 1790971620000,
"committed_at": 1790971620000,
"created_by": "usr_8k2m4q",
"errors": [],
"warnings": [
{
"line": 4,
"message": "pro_monthly in GBR changes by +55% (8.99 → 13.95). Check it is not a typo."
}
],
"summary": {
"price_changes": 2,
"new_products": 0,
"new_product_prices": 0,
"unchanged": 1,
"products": 1,
"rows": 3
},
"options": {
"preserve_current_price": true
},
"results": {
"pending": 0,
"succeeded": 1,
"failed": 1
},
"created_by_email": "dana@example.com"
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/product_edits"
}Upload a product file#
POST /v2/projects/{project_id}/product_edits · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
Checks a product file against what the store has now and keeps it. RevenueDot reads the store again (and refreshes the cached prices), then checks every line: the columns, each product, territory, currency and price, duplicate product and territory pairs, and the fields new products need. See every problem the check reports.
The answer is 201 either way:
status: "ready": every line is valid.rowslists every price that changes and every price of a new product;summarycounts them and the unchanged rows. Nothing is sent to the store until you commit.status: "invalid":errorslists every problem with its line, androwsis empty. The file stays on the Files tab; fix it and upload it again.
Rows whose price already matches the store are not changes. New values for the name, type, period or group of an existing product are ignored with a warning: the product editor changes prices only.
400 when csv is empty or larger than 1 MB, or app_id is not an app of the project. 422 unprocessable_entity_error for an app that is not on the App Store, Mac App Store or Google Play, or without the key changing prices needs (App Store Connect API key with the App Manager role; Play service account with "Manage store presence"); 422 store_error with retryable: true while the store cannot be reached.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_id |
string | yes | The App Store, Mac App Store or Google Play app the file is for. |
csv |
string | yes | The file's text, UTF-8. At most 1 MB and 20,000 rows. Comma separated, or semicolon or tab separated when the header line has no comma. |
file_name |
string | no | Shown on the Files tab. Default: products.csv. |
preserve_current_price |
boolean | no | App Store only. Default true: subscription price changes keep existing subscribers on their current price (Apple's preserveCurrentPrice). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"app_id":"app1a2b3c4d","file_name":"scanner-ios-app-store-products-2026-10-02.csv","csv":"store_identifier,display_name,type,duration,group,territory,currency,price,action\npro_monthly,Pro Monthly,subscription,P1M,Pro,USA,USD,10.99,\npro_monthly,Pro Monthly,subscription,P1M,Pro,DEU,EUR,9.99,\npro_monthly,Pro Monthly,subscription,P1M,Pro,GBR,GBP,8.99,\n"}'Responses
- 201: The file, checked:
readywith its changes, orinvalidwith its errors. Returns ProductEdit. - 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 422: The app is not an App Store, Mac App Store or Google Play app, or its store key is missing or refused (
unprocessable_entity_error, the message names the key and the role or permission it needs), or the store could not be reached (store_errorwithretryable: true). Returns V2Error.
Example 201 response:
{
"object": "product_edit",
"id": "pedit3k9x2m7q1z8w4c",
"app_id": "app1a2b3c4d",
"store": "app_store",
"status": "ready",
"file_name": "scanner-ios-app-store-products-2026-10-02.csv",
"created_at": 1790971500000,
"updated_at": 1790971500000,
"committed_at": null,
"created_by": "usr_8k2m4q",
"errors": [],
"warnings": [
{
"line": 4,
"message": "pro_monthly in GBR changes by +55% (8.99 → 13.95). Check it is not a typo."
}
],
"summary": {
"price_changes": 2,
"new_products": 0,
"new_product_prices": 0,
"unchanged": 1,
"products": 1,
"rows": 3
},
"options": {
"preserve_current_price": true
},
"results": {
"pending": 2,
"succeeded": 0,
"failed": 0
},
"rows": [
{
"object": "product_edit_row",
"idx": 0,
"kind": "price_change",
"line": 2,
"store_identifier": "pro_monthly",
"territory": "USA",
"currency": "USD",
"old_amount_micros": 9990000,
"new_amount_micros": 10990000,
"change_percent": 10,
"product": null,
"status": "pending",
"error": null,
"attempts": 0,
"updated_at": null
},
{
"object": "product_edit_row",
"idx": 1,
"kind": "price_change",
"line": 4,
"store_identifier": "pro_monthly",
"territory": "GBR",
"currency": "GBP",
"old_amount_micros": 8990000,
"new_amount_micros": 13950000,
"change_percent": 55.2,
"product": null,
"status": "pending",
"error": null,
"attempts": 0,
"updated_at": null
}
]
}Get a product file#
GET /v2/projects/{project_id}/product_edits/{edit_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read
The file with its errors, warnings, counts, options and every change with its result. Viewers can read it too.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
edit_id |
string | yes | Product file id (pedit...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits/$EDIT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The file. Returns ProductEdit.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "product_edit",
"id": "pedit3k9x2m7q1z8w4c",
"app_id": "app1a2b3c4d",
"store": "app_store",
"status": "partially_committed",
"file_name": "scanner-ios-app-store-products-2026-10-02.csv",
"created_at": 1790971500000,
"updated_at": 1790971620000,
"committed_at": 1790971620000,
"created_by": "usr_8k2m4q",
"errors": [],
"warnings": [
{
"line": 4,
"message": "pro_monthly in GBR changes by +55% (8.99 → 13.95). Check it is not a typo."
}
],
"summary": {
"price_changes": 2,
"new_products": 0,
"new_product_prices": 0,
"unchanged": 1,
"products": 1,
"rows": 3
},
"options": {
"preserve_current_price": true
},
"results": {
"pending": 0,
"succeeded": 1,
"failed": 1
},
"rows": [
{
"object": "product_edit_row",
"idx": 0,
"kind": "price_change",
"line": 2,
"store_identifier": "pro_monthly",
"territory": "USA",
"currency": "USD",
"old_amount_micros": 9990000,
"new_amount_micros": 10990000,
"change_percent": 10,
"product": null,
"status": "succeeded",
"error": null,
"attempts": 1,
"updated_at": 1790971620000
},
{
"object": "product_edit_row",
"idx": 1,
"kind": "price_change",
"line": 4,
"store_identifier": "pro_monthly",
"territory": "GBR",
"currency": "GBP",
"old_amount_micros": 8990000,
"new_amount_micros": 13950000,
"change_percent": 55.2,
"product": null,
"status": "failed",
"error": "The App Store has no price of 13.95 GBP in GBR. The nearest App Store prices are 13.49 and 13.99.",
"attempts": 1,
"updated_at": 1790971620000
}
],
"created_by_email": "dana@example.com"
}Change a product file's options#
POST /v2/projects/{project_id}/product_edits/{edit_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
App Store files only, before the commit. preserve_current_price: true (the default) keeps existing subscribers on their current price when a subscription's price changes. false moves them to the new price too; Apple tells them about an increase first and asks some of them to agree (Apple's rules). In-app purchase prices are not affected.
400 for a Google Play file. 409 invalid_request once the file is committed or when it has errors.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
edit_id |
string | yes | Product file id (pedit...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
preserve_current_price |
boolean | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits/$EDIT_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"preserve_current_price":false}'Responses
- 200: The file. Returns ProductEdit.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Discard a product file#
DELETE /v2/projects/{project_id}/product_edits/{edit_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
Removes a file that was never committed (ready or invalid) from the Files tab. Nothing was sent to the store. 409 invalid_request for a file that was committed: committed rows stay in the history.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
edit_id |
string | yes | Product file id (pedit...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits/$EDIT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Discarded. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "product_edit",
"id": "pedit3k9x2m7q1z8w4c",
"deleted_at": 1790971560000
}Commit a product file to the store#
POST /v2/projects/{project_id}/product_edits/{edit_id}/actions/commit · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
Writes the file's pending changes to App Store Connect or Google Play, one product at a time, and records each row's outcome.
- App Store subscription prices: the price point with exactly that customer price in that territory, then
POST /v1/subscriptionPriceswithpreserveCurrentPricefrom the file's options and no start date. A price with no price point fails, and the message names the two nearest App Store prices. - App Store in-app purchase prices: one
POST /v1/inAppPurchasePriceSchedulesper product with its base territory and every manual price: the ones it had plus the changed ones, so prices nobody changed stay. - Google Play base plan prices: one
monetization.subscriptions.patch(updateMask=basePlans) per subscription with the changed regional prices; the rest of the subscription is sent back as read. New prices apply to new subscribers; existing subscribers keep theirs until you move them in Play Console (Google's price changes). - New products: App Store subscriptions (in the subscription group named in
group, created when it does not exist) and in-app purchases are created, then priced as above. Google Play subscriptions are created with their base plan and regional prices, or the base plan is added to an existing subscription, and each new base plan is activated. Every new product is added to the catalog, like an import.
Rows that share one store call share its outcome. A row whose store price already equals the new price succeeds without a write. When the store refuses the key, every remaining row fails with that message. The answer's status is committed, partially_committed or failed; afterwards RevenueDot reads the store again so the cached prices are current.
One call writes for at most 20 seconds. When rows are left, it answers status: "committing": call commit again until the status changes. Each call is in the audit log as product_edit_commit, and each row adds store_price_changed (each new product store_product_created) with the territory, the old and new price and the result.
409 invalid_request when the file has errors or is already committed; 409 resource_locked_error while a commit of the same file, or of another file of the same app, runs. 422 unprocessable_entity_error when the app's key is missing.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
edit_id |
string | yes | Product file id (pedit...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits/$EDIT_ID/actions/commit" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The file after this call, with each row's result. Returns ProductEdit.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: The file has errors or is already committed (
invalid_request), or a commit of it or of another file of the same app is running (resource_locked_error). Returns V2Error. - 422: The app is not an App Store, Mac App Store or Google Play app, or its store key is missing or refused (
unprocessable_entity_error, the message names the key and the role or permission it needs), or the store could not be reached (store_errorwithretryable: true). Returns V2Error.
Example 200 response:
{
"object": "product_edit",
"id": "pedit3k9x2m7q1z8w4c",
"app_id": "app1a2b3c4d",
"store": "app_store",
"status": "partially_committed",
"file_name": "scanner-ios-app-store-products-2026-10-02.csv",
"created_at": 1790971500000,
"updated_at": 1790971620000,
"committed_at": 1790971620000,
"created_by": "usr_8k2m4q",
"errors": [],
"warnings": [
{
"line": 4,
"message": "pro_monthly in GBR changes by +55% (8.99 → 13.95). Check it is not a typo."
}
],
"summary": {
"price_changes": 2,
"new_products": 0,
"new_product_prices": 0,
"unchanged": 1,
"products": 1,
"rows": 3
},
"options": {
"preserve_current_price": true
},
"results": {
"pending": 0,
"succeeded": 1,
"failed": 1
},
"rows": [
{
"object": "product_edit_row",
"idx": 0,
"kind": "price_change",
"line": 2,
"store_identifier": "pro_monthly",
"territory": "USA",
"currency": "USD",
"old_amount_micros": 9990000,
"new_amount_micros": 10990000,
"change_percent": 10,
"product": null,
"status": "succeeded",
"error": null,
"attempts": 1,
"updated_at": 1790971620000
},
{
"object": "product_edit_row",
"idx": 1,
"kind": "price_change",
"line": 4,
"store_identifier": "pro_monthly",
"territory": "GBR",
"currency": "GBP",
"old_amount_micros": 8990000,
"new_amount_micros": 13950000,
"change_percent": 55.2,
"product": null,
"status": "failed",
"error": "The App Store has no price of 13.95 GBP in GBR. The nearest App Store prices are 13.49 and 13.99.",
"attempts": 1,
"updated_at": 1790971620000
}
]
}Retry the failed rows of a product file#
POST /v2/projects/{project_id}/product_edits/{edit_id}/actions/retry · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
Sets the failed rows back to pending and commits them again, like …/actions/commit. Rows that succeeded are not sent again. Products the first commit created are not created twice, and an App Store subscription row whose price is already in the store succeeds without a write. Logged as product_edit_retry.
409 invalid_request when no row failed; 409 resource_locked_error while a commit of the file, or of another file of the same app, runs.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
edit_id |
string | yes | Product file id (pedit...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/product_edits/$EDIT_ID/actions/retry" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The file after the retry. Returns ProductEdit.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: No row failed (
invalid_request), or a commit of the file or of another file of the same app is running (resource_locked_error). Returns V2Error. - 422: The app is not an App Store, Mac App Store or Google Play app, or its store key is missing or refused (
unprocessable_entity_error, the message names the key and the role or permission it needs), or the store could not be reached (store_errorwithretryable: true). Returns V2Error.
API keys#
Secret keys for the REST API.
List secret keys#
GET /v2/projects/{project_id}/api_keys · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:api_keys:read
Never returns the key itself.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/api_keys" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of results. Returns a list of ApiKey.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Create a secret key#
POST /v2/projects/{project_id}/api_keys · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:api_keys:read_write
The answer includes key once. permissions default to ["*"]. A key cannot create a key with permissions it does not hold.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | |
permissions |
array of string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/api_keys" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"name":"Backend (read only)","permissions":["customer_information:customers:read"]}'Responses
- 201: The key. Returns ApiKey.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 201 response:
{
"object": "api_key",
"id": "key_08ec817fce",
"name": "Backend (read only)",
"prefix": "sk_08ec",
"permissions": [
"customer_information:customers:read"
],
"created_at": 1790801342634,
"last_used_at": null,
"key": "sk_08ec817fceead27005772bb943c2bd225850eb931c9835f0"
}Delete a secret key#
DELETE /v2/projects/{project_id}/api_keys/{key_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:api_keys:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
key_id |
string | yes | Key id (key_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/api_keys/$KEY_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "api_key",
"id": "…",
"deleted_at": 1790801342625
}Webhook deliveries#
Delivery log, manual retry and test events.
Send a TEST event to one webhook#
POST /v2/projects/{project_id}/integrations/webhooks/{webhook_integration_id}/test · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Queues a purchase-shaped TEST event, signed and retried like any delivery. The webhook's filters do not apply. A paused webhook (enabled false) answers 422.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
webhook_integration_id |
string | yes | Webhook id. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/webhooks/$WEBHOOK_INTEGRATION_ID/test" -H "Authorization: Bearer $SECRET_KEY"Responses
- 201: The queued delivery. Returns WebhookDelivery.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Whether each webhook is enabled#
GET /v2/projects/{project_id}/webhooks · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
RevenueCat's webhook object has no enabled field, so it is read here. Set it with POST .../integrations/webhooks/{id}.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/webhooks" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of results. Returns a list of WebhookState.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delivery log of a webhook#
GET /v2/projects/{project_id}/webhooks/{webhook_id}/deliveries · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Newest first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
webhook_id |
string | yes | Webhook id (wh_...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
pending, delivered, failed |
no | |
paywall_events |
exclude, only |
no | Leave out, or return only, deliveries of paywall events (PAYWALL_*). Default: all. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/webhooks/$WEBHOOK_ID/deliveries" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of results. Returns a list of WebhookDelivery.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
One delivery: what was sent and every attempt#
GET /v2/projects/{project_id}/webhooks/{webhook_id}/deliveries/{delivery_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
The request as sent (method, URL, headers and the exact body; the Authorization value is masked) and every attempt, newest last (at most 10): when it was sent, the HTTP status, the latency, the error, the first 4,096 characters of the answer, and the signature header of that attempt. Answers and errors have the webhook's Authorization value (also the credential alone) and signing secret replaced, and anything that looks like a credential: bearer and basic credentials, passwords in URLs, token-like JSON fields and query parameters. curl repeats the request with a placeholder for Authorization.
Bodies can hold customer data, so this needs read_write (Admins and Developers). Attempt details are kept for attempt_log_kept_days (30) days after each attempt; the delivery itself stays.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
webhook_id |
string | yes | Webhook id. |
delivery_id |
string | yes | Delivery id. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/webhooks/$WEBHOOK_ID/deliveries/$DELIVERY_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The delivery.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Retry a delivery now#
POST /v2/projects/{project_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Queues the delivery now, also one waiting for its scheduled retry. 409 (resource_locked_error) while a job run is sending it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
webhook_id |
string | yes | Webhook id. |
delivery_id |
string | yes | Delivery id. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/webhooks/$WEBHOOK_ID/deliveries/$DELIVERY_ID/retry" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The delivery, queued. Returns WebhookDelivery.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Integrations#
Every tool of RevenueCat's integration catalogue plus BigQuery: 32 that receive events, and the AdMob, Apple Search Ads, Intercom inbox and Zendesk connections: the catalogue, connect, test, the delivery log, retry and replay. See Integrations.
What each integration needs#
GET /v2/projects/{project_id}/integrations/catalog · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
The fields each integration takes (keys, labels, types, options), its default environment, how RevenueDot reaches the partner (api), whether it is a connection without events, and its setup guide. The dashboard draws its forms from this. 36 entries: every tool of RevenueCat's integration catalogue plus BigQuery.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/catalog" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Every integration type.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "integration_type",
"type": "statsig",
"name": "Statsig",
"category": "analytics",
"description": "Send subscription events and revenue to Statsig to measure experiments and feature gates by what customers pay.",
"default_environment": null,
"event_names": true,
"fields": [
{
"key": "server_secret",
"label": "Server secret key",
"type": "secret",
"required": true,
"placeholder": "secret-…",
"hint": "In Statsig, Settings → Keys & Environments → Server Secret Key."
},
{
"key": "reporting",
"label": "Sales reporting",
"type": "select",
"options": [
{
"value": "gross",
"label": "Gross revenue"
},
{
"value": "proceeds",
"label": "After store commission and taxes"
}
],
"hint": "Revenue is sent in US dollars."
}
],
"docs_url": "https://revenuedot.app/docs/guides/integrations#statsig",
"api": "documented",
"connection": false
},
{
"object": "integration_type",
"type": "superwall",
"name": "Superwall",
"category": "analytics",
"description": "Send subscription events and revenue to Superwall so paywall reports show what each paywall earned.",
"default_environment": null,
"event_names": false,
"fields": [
{
"key": "webhook_url",
"label": "Superwall webhook URL",
"type": "secret",
"required": true,
"url": true
},
{
"key": "authorization",
"label": "Authorization header value",
"type": "secret"
}
],
"docs_url": "https://revenuedot.app/docs/guides/integrations#superwall",
"api": "webhook",
"connection": false
}
],
"next_page": null,
"url": "/v2/projects/proj1a2b3c4d/integrations/catalog"
}List integrations#
GET /v2/projects/{project_id}/integrations/partners · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
type |
slack, segment, amplitude, mixpanel, posthog, firebase, bigquery, appsflyer, adjust, meta, mparticle, statsig, superwall, telemetrydeck, apple_search_ads, appstack, asapty, branch, google_tag_manager, kochava, airbridge, splitmetrics, singular, solarengine, tenjin, airship, braze, clevertap, customerio, discord, intercom, iterable, onesignal, admob, intercom_inbox, zendesk |
no | |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The project's integrations.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Connect an integration#
POST /v2/projects/{project_id}/integrations/partners · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Every event webhooks get is also sent to each enabled integration whose filters match, with the webhook retry schedule (5, 10, 20, 40, 80 minutes). Secrets are encrypted at rest and never returned. The connections are saved here too: apple_search_ads with an Apple Search Ads API user (for campaign names), intercom_inbox with the Intercom app's client_secret, and zendesk with no settings (it marks the sidebar app as installed). AdMob connects through POST /v2/projects/{project_id}/ads/admob/connect.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
type |
slack, segment, amplitude, mixpanel, posthog, firebase, bigquery, appsflyer, adjust, meta, mparticle, statsig, superwall, telemetrydeck, apple_search_ads, appstack, asapty, branch, google_tag_manager, kochava, airbridge, splitmetrics, singular, solarengine, tenjin, airship, braze, clevertap, customerio, discord, intercom, iterable, onesignal, admob, intercom_inbox, zendesk |
yes | |
name |
string | no | |
enabled |
boolean | no | |
environment |
production, sandbox, null |
no | |
app_id |
string or null | no | |
event_types |
array of string | no | |
settings |
object | no | Every field of the integration's catalogue entry, secrets included. On update, a missing secret keeps its saved value; null removes it. |
event_names |
object | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"type":"amplitude","environment":null,"settings":{"api_key":"<amplitude api key>","region":"us"}}'Responses
- 201: The integration.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 201 response:
{
"object": "integration",
"id": "intg_8f2kq0x1m3zv7a",
"project_id": "proj1a2b3c4d",
"type": "amplitude",
"name": "Amplitude",
"enabled": true,
"environment": null,
"app_id": null,
"event_types": [],
"settings": {
"region": "us"
},
"secrets": {
"api_key": {
"configured": true,
"hint": "••••9f3a"
},
"sandbox_api_key": {
"configured": false,
"hint": null
}
},
"event_names": {},
"status": {
"last_delivered_at": null,
"last_error": null,
"consecutive_failures": 0,
"failed_deliveries_in_row": 0
},
"created_at": 1790850000000,
"updated_at": 1790850000000
}Get an integration#
GET /v2/projects/{project_id}/integrations/partners/{integration_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The integration.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Update, enable or disable an integration#
POST /v2/projects/{project_id}/integrations/partners/{integration_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
enabled: false stops new deliveries; failed ones wait until it is on again.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
type |
slack, segment, amplitude, mixpanel, posthog, firebase, bigquery, appsflyer, adjust, meta, mparticle, statsig, superwall, telemetrydeck, apple_search_ads, appstack, asapty, branch, google_tag_manager, kochava, airbridge, splitmetrics, singular, solarengine, tenjin, airship, braze, clevertap, customerio, discord, intercom, iterable, onesignal, admob, intercom_inbox, zendesk |
no | |
name |
string | no | |
enabled |
boolean | no | |
environment |
production, sandbox, null |
no | |
app_id |
string or null | no | |
event_types |
array of string | no | |
settings |
object | no | Every field of the integration's catalogue entry, secrets included. On update, a missing secret keeps its saved value; null removes it. |
event_names |
object | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"enabled":false}'Responses
- 200: The integration.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Disconnect an integration#
DELETE /v2/projects/{project_id}/integrations/partners/{integration_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Its delivery log goes with it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Send a TEST event to one integration#
POST /v2/projects/{project_id}/integrations/partners/{integration_id}/test · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
With app_user_id, the event carries that customer's attributes, so attribution partners (which need $appsflyerId, $adjustId, $fbAnonId ...) can be tested. A disabled integration answers 422.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_user_id |
string | no | |
environment |
production, sandbox |
no | |
product_id |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID/test" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{}'Responses
- 201: The queued delivery.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Delivery log of an integration#
GET /v2/projects/{project_id}/integrations/partners/{integration_id}/deliveries · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Newest first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
pending, delivered, failed, skipped |
no | |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID/deliveries" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deliveries.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
One delivery with every attempt#
GET /v2/projects/{project_id}/integrations/partners/{integration_id}/deliveries/{delivery_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
The delivery with every attempt, newest last (at most 10): when, HTTP status, latency, error, the requests' methods and URLs and the first 4,096 characters of the answer. Credentials are removed: the integration's own secrets, and anything that looks like one (bearer and basic credentials, passwords in URLs, token-like JSON fields and query parameters). curl repeats a single-request delivery with the right Content-Type and a placeholder for the partner's credentials; it is null when the delivery made several requests, when the URL is itself the secret (Slack, Discord) or when the body was longer than the log keeps. Needs read_write (Admins and Developers): bodies can hold customer data, and the log answers Viewers without request_body and response_body. Attempt details are kept for 30 days after each attempt.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
delivery_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID/deliveries/$DELIVERY_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The delivery.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Retry a delivery now#
POST /v2/projects/{project_id}/integrations/partners/{integration_id}/deliveries/{delivery_id}/retry · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
The request is built again, so keys and attributes saved since count. 409 (resource_locked_error) while a job run is sending it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
delivery_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID/deliveries/$DELIVERY_ID/retry" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The delivery, queued.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Queue failed or skipped deliveries again#
POST /v2/projects/{project_id}/integrations/partners/{integration_id}/actions/replay · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
integration_id |
string | yes | Integration id (intg_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
status |
failed, skipped, failed_and_skipped |
no | Default failed. |
since |
integer | no | Queued at or after (epoch ms). |
until |
integer | no | Queued at or before (epoch ms). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/partners/$INTEGRATION_ID/actions/replay" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"status":"failed_and_skipped"}'Responses
- 200: How many were queued.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Ads#
The Ads Overview (ad revenue, impressions, eCPM and breakdowns from the SDK's ad events), rewarded ads verified on the server (AdMob's callback, reward rules, the rewards ledger, test rewards), the AdMob connection and Apple Search Ads campaign reporting. See Ads.
Ads Overview: ad revenue, impressions, eCPM and breakdowns#
GET /v2/projects/{project_id}/ads/overview · Auth: secret key or dashboard session · RevenueDot extension · Permissions: charts_metrics:overview:read
Totals, the previous period, a daily series and breakdowns by network, format, placement, ad unit and mediator, from the ad events the SDK posts to POST /v1/events. Money is US dollars at each day's exchange rate.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
range |
7d, 28d, 90d, 12m |
no | Default 28d. Periods are UTC days ending today. |
environment |
production, sandbox |
no | Default production. |
app_id |
string | no | Only this app's events and purchases. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/overview" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The overview.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "ads_overview",
"currency": "USD",
"range": "7d",
"environment": "production",
"app_id": null,
"start_date": "2026-09-25",
"end_date": "2026-10-01",
"has_ad_events": true,
"totals": {
"ad_revenue": 412.37,
"impressions": 183240,
"ecpm": 2.25,
"clicks": 2210,
"ctr": 0.0121,
"loaded": 190112,
"failed_to_load": 8410,
"fill_rate": 0.9576,
"revenue_events": 183240,
"ad_customers": 9312,
"subscription_revenue": 1840.5,
"total_revenue": 2252.87,
"ad_share": 0.183
},
"previous": {
"ad_revenue": 388.1,
"impressions": 176005,
"ecpm": 2.21,
"clicks": 2034,
"ctr": 0.0116,
"loaded": 181220,
"failed_to_load": 8102,
"fill_rate": 0.9572,
"revenue_events": 176005,
"subscription_revenue": 1702.25
},
"series": [
{
"date": "2026-09-25",
"ad_revenue": 57.9,
"impressions": 25811,
"ecpm": 2.24,
"clicks": 301,
"subscription_revenue": 255.3
}
],
"by_network": [
{
"key": "AdMob",
"ad_revenue": 301.2,
"impressions": 131002,
"ecpm": 2.3,
"clicks": 1650,
"share": 0.7304
}
],
"by_format": [
{
"key": "rewarded",
"ad_revenue": 250.11,
"impressions": 60321,
"ecpm": 4.15,
"clicks": 801,
"share": 0.6065
}
],
"by_placement": [
{
"key": "level_end",
"ad_revenue": 180.4,
"impressions": 41022,
"ecpm": 4.4,
"clicks": 512,
"share": 0.4375
}
],
"by_ad_unit": [
{
"key": "ca-app-pub-3940256099942544/5224354917",
"ad_revenue": 180.4,
"impressions": 41022,
"ecpm": 4.4,
"clicks": 512,
"share": 0.4375,
"name": "Level end rewarded",
"unit_format": "rewarded"
}
],
"by_mediator": [
{
"key": "AdMob",
"ad_revenue": 412.37,
"impressions": 183240,
"ecpm": 2.25,
"clicks": 2210,
"share": 1
}
],
"unconverted": [],
"ad_units_loaded": 6
}List reward rules in the order they are checked#
GET /v2/projects/{project_id}/ads/reward_rules · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_rules" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Every rule, first checked first.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Create a reward rule#
POST /v2/projects/{project_id}/ads/reward_rules · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
The new rule goes last. A project has at most 200 rules. A currency rule needs currency_code and amount or multiplier; an entitlement rule needs entitlement_id and duration_minutes.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | |
enabled |
boolean | no | A disabled rule is skipped. Default true. |
app_id |
string or null | no | Only rewards from this app; null matches every app. |
ad_unit_id |
string or null | no | Only this ad unit. AdMob's callback sends the number after the slash (5224354917) and the SDK the full id (ca-app-pub-…/5224354917); either form matches. Null or empty matches every ad unit. |
reward_item |
string or null | no | Only this AdMob reward item (reward_item, compared without case). Null or empty matches every item. |
kind |
virtual_currency, entitlement |
yes | What the rule grants. |
currency_code |
string or null | no | The in-app currency to credit (kind virtual_currency). It must exist in the project. |
amount |
integer or null | no | A fixed amount per reward, 1 to 1,000,000,000 (kind virtual_currency). |
multiplier |
number or null | no | Instead of amount: the ad network's reward_amount times this, rounded, at least 1 (kind virtual_currency). |
entitlement_id |
string or null | no | The entitlement's lookup key (kind entitlement). It must exist in the project. |
duration_minutes |
integer or null | no | How long the entitlement lasts, 1 to 525,600 minutes (kind entitlement). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_rules" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"name":"Gems for level-end ads","ad_unit_id":"ca-app-pub-3940256099942544/5224354917","kind":"virtual_currency","currency_code":"GEMS","amount":10}'Responses
- 201: The rule.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 201 response:
{
"object": "ad_reward_rule",
"id": "adrr_4kq0x1m3zv7a2b",
"name": "Gems for level-end ads",
"enabled": true,
"position": 0,
"app_id": null,
"ad_unit_id": "ca-app-pub-3940256099942544/5224354917",
"reward_item": null,
"kind": "virtual_currency",
"currency_code": "GEMS",
"amount": 10,
"multiplier": null,
"entitlement_id": null,
"duration_minutes": null,
"created_at": 1790850000000,
"updated_at": null
}Reorder reward rules#
POST /v2/projects/{project_id}/ads/reward_rules/actions/reorder · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
rule_ids |
array of string | yes | Every rule id of the project exactly once, in the new order. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_rules/actions/reorder" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"rule_ids":["adrr_8m2v0q1x4k7z3a","adrr_4kq0x1m3zv7a2b"]}'Responses
- 200: Every rule in the new order.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Update, turn on or turn off a reward rule#
POST /v2/projects/{project_id}/ads/reward_rules/{rule_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Send only what changes. Switching kind clears the other kind's fields. Rewards already granted stay.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
rule_id |
string | yes | Reward rule id (adrr_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
enabled |
boolean | no | A disabled rule is skipped. Default true. |
app_id |
string or null | no | Only rewards from this app; null matches every app. |
ad_unit_id |
string or null | no | Only this ad unit. AdMob's callback sends the number after the slash (5224354917) and the SDK the full id (ca-app-pub-…/5224354917); either form matches. Null or empty matches every ad unit. |
reward_item |
string or null | no | Only this AdMob reward item (reward_item, compared without case). Null or empty matches every item. |
kind |
virtual_currency, entitlement |
no | What the rule grants. |
currency_code |
string or null | no | The in-app currency to credit (kind virtual_currency). It must exist in the project. |
amount |
integer or null | no | A fixed amount per reward, 1 to 1,000,000,000 (kind virtual_currency). |
multiplier |
number or null | no | Instead of amount: the ad network's reward_amount times this, rounded, at least 1 (kind virtual_currency). |
entitlement_id |
string or null | no | The entitlement's lookup key (kind entitlement). It must exist in the project. |
duration_minutes |
integer or null | no | How long the entitlement lasts, 1 to 525,600 minutes (kind entitlement). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_rules/$RULE_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"enabled":false}'Responses
- 200: The rule.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "ad_reward_rule",
"id": "adrr_4kq0x1m3zv7a2b",
"name": "Gems for level-end ads",
"enabled": false,
"position": 0,
"app_id": null,
"ad_unit_id": "ca-app-pub-3940256099942544/5224354917",
"reward_item": null,
"kind": "virtual_currency",
"currency_code": "GEMS",
"amount": 10,
"multiplier": null,
"entitlement_id": null,
"duration_minutes": null,
"created_at": 1790850000000,
"updated_at": 1790853600000
}Delete a reward rule#
DELETE /v2/projects/{project_id}/ads/reward_rules/{rule_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
rule_id |
string | yes | Reward rule id (adrr_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_rules/$RULE_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
The rewards ledger#
GET /v2/projects/{project_id}/ads/reward_verifications · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
One row per verified callback (and per test reward), newest first, with what it granted.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
verified, failed, pending |
no | |
app_user_id |
string | no | Only this customer's rewards. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_verifications" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Verifications.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "ad_reward_verification",
"id": "adrw_9c2kd81mzq0x4v7a",
"app_id": "app1a2b3c4d",
"app_user_id": "user_42",
"client_transaction_id": "5C1A4F0E-2B7D-4C11-9A3E-0F6B8D2E7A91",
"network": "admob",
"network_transaction_id": "18fa792de1bca816048293fc71035638",
"ad_unit_id": "5224354917",
"impression_id": "imp_7f3a",
"reward_item": "coins",
"reward_amount": 10,
"status": "verified",
"failure_reason": null,
"failure_message": null,
"rule_id": "adrr_4kq0x1m3zv7a2b",
"rewards": [
{
"type": "virtual_currency",
"code": "GEMS",
"amount": 10
}
],
"is_sandbox": false,
"occurred_at": 1790850000000,
"created_at": 1790850000420
}
],
"next_page": null,
"url": "/v2/projects/proj1a2b3c4d/ads/reward_verifications"
}Send a test reward#
POST /v2/projects/{project_id}/ads/reward_verifications/test · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write, customer_information:purchases:read_write
Runs the same rules and grants as a verified AdMob callback, without an ad: network test, marked sandbox. The currency or access is granted for real, so the key also needs customer_information:purchases:read_write, like a balance adjustment. With client_transaction_id, the SDK's poll for that id answers with this reward.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_user_id |
string | yes | The customer to reward. |
app_id |
string or null | no | Match rules for this app. |
ad_unit_id |
string or null | no | |
reward_item |
string or null | no | |
reward_amount |
integer or null | no | The network amount a multiplier rule multiplies. |
client_transaction_id |
string | no | Default: a new UUID. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/reward_verifications/test" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"app_user_id":"user_42","reward_item":"coins","reward_amount":5}'Responses
- 201: The verification.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 201 response:
{
"object": "ad_reward_verification",
"id": "adrw_1x0q8m2v4k7z3abc",
"app_id": "app1a2b3c4d",
"app_user_id": "user_42",
"client_transaction_id": "5C1A4F0E-2B7D-4C11-9A3E-0F6B8D2E7A91",
"network": "test",
"network_transaction_id": "test_proj1a2b3c4d_8E2F…",
"ad_unit_id": null,
"impression_id": null,
"reward_item": "coins",
"reward_amount": 5,
"status": "verified",
"failure_reason": null,
"failure_message": null,
"rule_id": "adrr_4kq0x1m3zv7a2b",
"rewards": [
{
"type": "virtual_currency",
"code": "GEMS",
"amount": 10
}
],
"is_sandbox": true,
"occurred_at": 1790850000000,
"created_at": 1790850000420
}AdMob connection, loaded ad units and the URLs to paste#
GET /v2/projects/{project_id}/ads/admob · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/admob" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The connection.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "admob_connection",
"connected": true,
"oauth_client": "server",
"client_id": null,
"client_secret": {
"configured": false,
"hint": null
},
"connected_at": 1790850000000,
"accounts": [
{
"id": "pub-3940256099942544",
"currency": "USD"
}
],
"last_sync_at": 1790936400000,
"last_sync_error": null,
"ad_units": [
{
"ad_unit_id": "ca-app-pub-3940256099942544/5224354917",
"name": "Level end rewarded",
"format": "rewarded",
"account_id": "pub-3940256099942544",
"app_id": "ca-app-pub-3940256099942544~1458002511",
"updated_at": 1790936400000
}
],
"redirect_uri": "https://api.revenuedot.app/v1/ads/admob/oauth/callback",
"ssv_callback_url": "https://api.revenuedot.app/v1/ads/admob/ssv"
}Disconnect AdMob#
DELETE /v2/projects/{project_id}/ads/admob · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Deletes the Google tokens and the loaded ad units. Ad revenue from the SDK keeps working; rewarded-ad verification then accepts only the ad units named on reward rules.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/admob" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The connection, now empty.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Start Google sign-in for AdMob#
POST /v2/projects/{project_id}/ads/admob/connect · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Returns Google's authorization URL (scope https://www.googleapis.com/auth/admob.readonly, offline access) and a nonce. Keep the nonce in the browser that opens the URL. Google redirects to /v1/ads/admob/oauth/callback, which sends the code on to the project's AdMob page in the URL fragment; that page finishes with finishAdMobConnect and the nonce. So a sign-in link someone else started cannot connect your Google account to their project. The link works once, for 10 minutes. Without a server OAuth client, send the project's own client_id and client_secret (422 otherwise).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
client_id |
string or null | no | A Google OAuth client id (…apps.googleusercontent.com). Null removes the project's own client. |
client_secret |
string or null | no | Its secret. Leave out to keep the saved one. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/admob/connect" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{}'Responses
- 200: Google's sign-in URL and the browser's nonce.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
- 502: An upstream service (the store or the language model) gave no usable answer. Retry later. Returns V2Error.
Example 200 response:
{
"object": "admob_authorization",
"nonce": "4f1c…",
"url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=…&redirect_uri=https%3A%2F%2Fapi.revenuedot.app%2Fv1%2Fads%2Fadmob%2Foauth%2Fcallback&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadmob.readonly&access_type=offline&prompt=consent&include_granted_scopes=true&state=…"
}Finish Google sign-in for AdMob#
POST /v2/projects/{project_id}/ads/admob/finish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
The AdMob page calls this with the admob_code and admob_state from its URL fragment and the nonce from connectAdMob. RevenueDot checks that the state is this project's pending sign-in (single use, 10 minutes) and that the nonce matches, exchanges the code for a refresh token (stored encrypted) and loads the ad units. 400 when the state or nonce is wrong, used or expired.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | admob_code from the fragment. |
state |
string | yes | admob_state from the fragment. |
nonce |
string | yes | From connectAdMob. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/admob/finish" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"code":"4/0Ab…","state":"proj1a2b3c4d.9f2c…","nonce":"4f1c…"}'Responses
- 200: The connection.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
- 502: An upstream service (the store or the language model) gave no usable answer. Retry later. Returns V2Error.
Example 200 response:
{
"object": "admob_connection",
"connected": true,
"oauth_client": "server",
"client_id": null,
"client_secret": {
"configured": false,
"hint": null
},
"connected_at": 1790850000000,
"accounts": [
{
"id": "pub-3940256099942544",
"currency": "USD"
}
],
"last_sync_at": 1790936400000,
"last_sync_error": null,
"ad_units": [
{
"ad_unit_id": "ca-app-pub-3940256099942544/5224354917",
"name": "Level end rewarded",
"format": "rewarded",
"account_id": "pub-3940256099942544",
"app_id": "ca-app-pub-3940256099942544~1458002511",
"updated_at": 1790936400000
}
],
"redirect_uri": "https://api.revenuedot.app/v1/ads/admob/oauth/callback",
"ssv_callback_url": "https://api.revenuedot.app/v1/ads/admob/ssv"
}Load AdMob ad units now#
POST /v2/projects/{project_id}/ads/admob/refresh · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Replaces the loaded ad units with the account's current list (up to 5,000). RevenueDot also does this once a day.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/admob/refresh" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The connection.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 502: An upstream service (the store or the language model) gave no usable answer. Retry later. Returns V2Error.
Example 200 response:
{
"object": "admob_connection",
"connected": true,
"oauth_client": "server",
"client_id": null,
"client_secret": {
"configured": false,
"hint": null
},
"connected_at": 1790850000000,
"accounts": [
{
"id": "pub-3940256099942544",
"currency": "USD"
}
],
"last_sync_at": 1790936400000,
"last_sync_error": null,
"ad_units": [
{
"ad_unit_id": "ca-app-pub-3940256099942544/5224354917",
"name": "Level end rewarded",
"format": "rewarded",
"account_id": "pub-3940256099942544",
"app_id": "ca-app-pub-3940256099942544~1458002511",
"updated_at": 1790936400000
}
],
"redirect_uri": "https://api.revenuedot.app/v1/ads/admob/oauth/callback",
"ssv_callback_url": "https://api.revenuedot.app/v1/ads/admob/ssv"
}Customers and revenue by Apple Search Ads campaign#
GET /v2/projects/{project_id}/ads/apple_search_ads/report · Auth: secret key or dashboard session · RevenueDot extension · Permissions: charts_metrics:overview:read
Customers first seen in the period whose $appleAdsCampaignId is set (from the SDK's AdServices token), grouped by campaign, with paying customers and production revenue to date.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
range |
7d, 28d, 90d, 12m |
no | Default 90d. Customers first seen in this period. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/apple_search_ads/report" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The report.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "apple_search_ads_report",
"range": "90d",
"start_date": "2026-07-04",
"currency": "USD",
"campaigns": [
{
"campaign_id": "1234567890",
"name": "Brand US",
"customers": 412,
"paying_customers": 38,
"revenue": 1204.55,
"revenue_per_customer": 2.92
}
],
"names_loaded": 12,
"last_sync_at": 1790850000000,
"last_sync_error": null
}Load campaign names from Apple Search Ads#
POST /v2/projects/{project_id}/ads/apple_search_ads/sync · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Signs in as the Apple Search Ads API user saved on the project's apple_search_ads integration (organization ID, client ID, team ID, key ID, private key) and loads campaign and ad group names from the Campaign Management API v5 (up to 200 campaigns). 422 with Apple's message when the credentials are missing or refused.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ads/apple_search_ads/sync" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: How many campaigns were named.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "apple_search_ads_sync",
"campaigns": 12
}AdMob server-side verification callback#
GET /v1/ads/admob/ssv · Auth: none · RevenueDot extension
The URL to paste into each rewarded ad unit's server-side verification settings in AdMob. Google calls it with a signed query (ad_network, ad_unit, custom_data, key_id, reward_amount, reward_item, signature, timestamp, transaction_id, user_id; AdMob SSV); RevenueDot checks the ECDSA signature with Google's published keys, finds the project from the app key in custom_data (the SDK's reward verification token), checks that ad_unit is one of the project's ad units (loaded by the AdMob connection, or named on a reward rule), records the reward once per AdMob transaction_id and grants what the first matching reward rule says. Only the signed part of the query is read. Google signs every publisher's callbacks with the same keys, and the app key ships inside the app, so another AdMob account's callback with your app key is recorded as failed (unknown_ad_unit) and grants nothing.
- 200
{"ok":true,"recorded":true}: recorded (or already recorded). Also 200 with no parameters (AdMob's Verify URL button), and 200{"ok":true,"recorded":false,"reason":"invalid_custom_data"}or"unknown_api_key"whencustom_datais not a RevenueDot token, so Google stops retrying. - 400: the query does not end with
signatureandkey_id, has anything afterkey_id, or repeats a parameter. 403: the signature is not valid or the key id is unknown; nothing is recorded. - 503: Google's keys could not be fetched. Google retries callbacks that do not answer 200.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
ad_unit |
string | no | |
custom_data |
string | no | |
key_id |
string | no | |
reward_amount |
string | no | |
reward_item |
string | no | |
signature |
string | no | |
timestamp |
string | no | |
transaction_id |
string | no | |
user_id |
string | no |
Example request
curl -s "$REVENUEDOT_URL/v1/ads/admob/ssv"Responses
- 200: Recorded, or nothing to record.
- 400: Not an AdMob callback.
- 403: Bad signature.
- 503: Google's keys are unavailable.
Example 200 response:
{
"ok": true,
"recorded": true
}Google's redirect after AdMob sign-in#
GET /v1/ads/admob/oauth/callback · Auth: none · RevenueDot extension
Add <API origin>/v1/ads/admob/oauth/callback as an authorized redirect URI of the Google OAuth client. RevenueDot redirects to the project's AdMob page with the code and state in the URL fragment (#admob_code=…&admob_state=…, never sent to a server), and the page finishes with finishAdMobConnect. A cancelled sign-in redirects with ?admob_error=<message>.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
code |
string | no | |
state |
string | no | From connectAdMob; single use, valid 10 minutes. |
error |
string | no | access_denied when the user cancelled. |
Example request
curl -s "$REVENUEDOT_URL/v1/ads/admob/oauth/callback"Responses
- 302: To the dashboard's AdMob page.
Data exports#
Scheduled CSV or Parquet files of transactions, customers, subscriptions and events in Amazon S3, Cloudflare R2 or Google Cloud Storage.
List scheduled data exports#
GET /v2/projects/{project_id}/integrations/exports · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The project's exports.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Create a scheduled data export#
POST /v2/projects/{project_id}/integrations/exports · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
CSV or Parquet files of transactions, customers, subscriptions, events and paywall events, written every 4, 6, 8 or 12 hours, daily or weekly to Amazon S3 (or any S3-compatible storage), Cloudflare R2, Google Cloud Storage or Azure Blob Storage under <prefix>/<YYYY-MM-DD>/<table>_<YYYYMMDDTHHMMSSZ>.<ext>, or kept by RevenueDot for 7 days and emailed as download links to up to 25 members of the project (destination email). columns picks each table's columns. Incremental exports write rows that changed since the previous run; each table's first run is complete.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
enabled |
boolean | no | |
destination |
s3, r2, gcs, azure, email |
yes | |
config |
object | yes | bucket is the container for Azure. Email exports have no bucket. |
config.bucket |
string | no | |
config.prefix |
string or null | no | |
config.region |
string or null | no | S3 region (default us-east-1). |
config.endpoint |
string or null | no | S3-compatible endpoint (MinIO and the like). |
config.account_id |
string or null | no | Cloudflare account id (R2). |
config.access_key_id |
string or null | no | S3, R2 or GCS HMAC access key id. |
config.credential_type |
service_account, hmac, null |
no | GCS only: a service account JSON key (default) or an HMAC key. |
config.recipients |
array of string | no | Email only: up to 25 addresses, each a member of the project. |
config.subject_prefix |
string or null | no | Email only: put before the email subject (up to 200 characters). |
credentials |
object | no | |
credentials.secret_access_key |
string or null | no | |
credentials.service_account_json |
string or null | no | The service account's JSON key as a string. |
credentials.connection_string |
string or null | no | Azure: the storage account's connection string (account key or shared access signature). |
format |
csv, parquet |
no | |
compression |
gzip, none |
no | |
schedule |
daily, weekly, interval |
no | |
hour_utc |
integer | no | |
weekday |
integer or null | no | |
interval_hours |
integer or null | no | 4, 6, 8 or 12. |
mode |
incremental, full |
no | |
split_files |
boolean | no | CSV only: split every 10,000 rows instead of one file per table. |
tables |
array of transactions, customers, subscriptions, events, paywall_events, virtual_currency |
no | |
columns |
object | no | Table → the columns to write, in the catalog's order (see List export columns). A table left out, or an empty list, gets every column, columns added later included. |
environment |
production, sandbox, null |
no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"name":"Warehouse","destination":"s3","config":{"bucket":"acme-exports","prefix":"revenuedot","region":"eu-west-1","access_key_id":"AKIA..."},"credentials":{"secret_access_key":"<secret>"},"format":"csv","schedule":"daily","hour_utc":3,"tables":["transactions"]}'Responses
- 201: The export.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
List export columns#
GET /v2/projects/{project_id}/integrations/exports/columns · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Every table an export can write, with its columns and their types, in the order files use. Pick columns per table with columns.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/columns" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The tables.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Download a file of an email export#
GET /v2/data-exports/download/{token} · Auth: none · RevenueDot extension
One file of an email export run, as emailed to the recipients. The token is signed and is the only auth; it and the file last 7 days.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/data-exports/download/$TOKEN"Responses
- 200: The file (
.csv.gz,.csvor.parquet). - 404: The link has expired or is not valid.
Get a data export#
GET /v2/projects/{project_id}/integrations/exports/{export_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes | Export id (export_...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/$EXPORT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The export.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Update a data export#
POST /v2/projects/{project_id}/integrations/exports/{export_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes | Export id (export_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
enabled |
boolean | no | |
destination |
s3, r2, gcs, azure, email |
no | |
config |
object | no | bucket is the container for Azure. Email exports have no bucket. |
config.bucket |
string | no | |
config.prefix |
string or null | no | |
config.region |
string or null | no | S3 region (default us-east-1). |
config.endpoint |
string or null | no | S3-compatible endpoint (MinIO and the like). |
config.account_id |
string or null | no | Cloudflare account id (R2). |
config.access_key_id |
string or null | no | S3, R2 or GCS HMAC access key id. |
config.credential_type |
service_account, hmac, null |
no | GCS only: a service account JSON key (default) or an HMAC key. |
config.recipients |
array of string | no | Email only: up to 25 addresses, each a member of the project. |
config.subject_prefix |
string or null | no | Email only: put before the email subject (up to 200 characters). |
credentials |
object | no | |
credentials.secret_access_key |
string or null | no | |
credentials.service_account_json |
string or null | no | The service account's JSON key as a string. |
credentials.connection_string |
string or null | no | Azure: the storage account's connection string (account key or shared access signature). |
format |
csv, parquet |
no | |
compression |
gzip, none |
no | |
schedule |
daily, weekly, interval |
no | |
hour_utc |
integer | no | |
weekday |
integer or null | no | |
interval_hours |
integer or null | no | 4, 6, 8 or 12. |
mode |
incremental, full |
no | |
split_files |
boolean | no | CSV only: split every 10,000 rows instead of one file per table. |
tables |
array of transactions, customers, subscriptions, events, paywall_events, virtual_currency |
no | |
columns |
object | no | Table → the columns to write, in the catalog's order (see List export columns). A table left out, or an empty list, gets every column, columns added later included. |
environment |
production, sandbox, null |
no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/$EXPORT_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"schedule":"weekly","weekday":1}'Responses
- 200: The export.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete a data export#
DELETE /v2/projects/{project_id}/integrations/exports/{export_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Files already written stay in the bucket. Files RevenueDot keeps for an email export are deleted.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes | Export id (export_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/$EXPORT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Run an export now#
POST /v2/projects/{project_id}/integrations/exports/{export_id}/actions/run · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes | Export id (export_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
mode |
incremental, full |
no | Default: the export's mode. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/$EXPORT_ID/actions/run" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{}'Responses
- 201: The queued run.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Check the bucket and credentials#
POST /v2/projects/{project_id}/integrations/exports/{export_id}/actions/check · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read_write
S3, R2 and GCS with an HMAC key: HeadBucket. GCS with a service account: buckets.get. Azure: Get Container Properties. Email: always ok.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes | Export id (export_...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/$EXPORT_ID/actions/check" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The result.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Run history#
GET /v2/projects/{project_id}/integrations/exports/{export_id}/runs · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:integrations:read
Newest first, with each file written.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes | Export id (export_...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/integrations/exports/$EXPORT_ID/runs" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Runs.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Refund Control#
Policies that answer Apple's refund requests with consumption information, the refund request log and its cards.
Get policies and settings#
GET /v2/projects/{project_id}/refund_control · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/refund_control" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Refund Control.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Save settings and the ordered policies#
POST /v2/projects/{project_id}/refund_control · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Replaces the settings you send and, when policies is present, the whole ordered list: the array order is the evaluation order; policies with an id are kept, the others created, the missing ones deleted.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
settings |
object | no | |
settings.default_preference |
prefer_refund, prefer_prorated_refund, prefer_no_refund, consumption_only, do_not_respond |
no | Send Consumption Information (V2): prefer_refund sends refundPreference GRANT_FULL, prefer_prorated_refund GRANT_PRORATED (or GRANT_FULL at 0% used, DECLINE at 100% used, no preference when the use is unknown, outside auto-renewable subscriptions), prefer_no_refund DECLINE, consumption_only no refundPreference. V1 (Advanced Commerce API purchases): 1, 1, 2 and 0. do_not_respond sends nothing. |
settings.customer_consented |
boolean | no | You confirm customers agreed to share consumption data. Apple requires it; without it nothing is sent. |
policies |
array of object | no | |
policies[].id |
string | no | |
policies[].name |
string | yes | |
policies[].template |
string | no | |
policies[].rules |
object | yes | |
policies[].rules.groups |
array of object | yes | Groups are OR-ed; conditions in a group are AND-ed. No groups matches everyone. |
policies[].preference |
prefer_refund, prefer_prorated_refund, prefer_no_refund, consumption_only, do_not_respond |
yes | Send Consumption Information (V2): prefer_refund sends refundPreference GRANT_FULL, prefer_prorated_refund GRANT_PRORATED (or GRANT_FULL at 0% used, DECLINE at 100% used, no preference when the use is unknown, outside auto-renewable subscriptions), prefer_no_refund DECLINE, consumption_only no refundPreference. V1 (Advanced Commerce API purchases): 1, 1, 2 and 0. do_not_respond sends nothing. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/refund_control" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"settings":{"customer_consented":true,"default_preference":"consumption_only"},"policies":[{"name":"Recent renewals","template":"recent_renewal","rules":{"groups":[{"conditions":[{"field":"lastRenewalAt","operator":"within","value":"24h"}]}]},"preference":"prefer_no_refund"}]}'Responses
- 200: Refund Control.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Refund rate, amounts and counts#
GET /v2/projects/{project_id}/refund_control/stats · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
days |
integer | no | |
environment |
production, sandbox |
no |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/refund_control/stats" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The cards.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
List refund requests, newest first#
GET /v2/projects/{project_id}/refund_requests · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Apple CONSUMPTION_REQUEST notifications with the consumption information sent, and refunds learned of without a request (Apple REFUND or REFUND_DECLINED, Google voided purchases and chargebacks, Stripe and Amazon refunds).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/refund_requests" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Requests.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Retention#
Customer Center cancel and refund offers, and Apple's Retention Messaging API (messages, defaults, the real-time call).
List Customer Center retention offers#
GET /v2/projects/{project_id}/retention_offers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/retention_offers" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Offers.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Create a retention offer#
POST /v2/projects/{project_id}/retention_offers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Active cancel offers are added as promotional_offer to the Customer Center's CANCEL paths, refund offers to its REFUND_REQUEST paths.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
trigger |
cancel, refund |
yes | |
name |
string | yes | |
title |
string | yes | |
subtitle |
string | no | |
store |
app_store, play_store |
yes | |
product_mapping |
object | yes | |
active |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/retention_offers" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"trigger":"cancel","name":"Cancellation discount","title":"Stay for 50% off","subtitle":"Three months at half price","store":"app_store","product_mapping":{"pro_monthly":"stay_50"}}'Responses
- 201: The offer.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Update a retention offer#
POST /v2/projects/{project_id}/retention_offers/{offer_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
offer_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
trigger |
cancel, refund |
no | |
name |
string | no | |
title |
string | no | |
subtitle |
string | no | |
store |
app_store, play_store |
no | |
product_mapping |
object | no | |
active |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/retention_offers/$OFFER_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"active":false}'Responses
- 200: The offer.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete a retention offer#
DELETE /v2/projects/{project_id}/retention_offers/{offer_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
offer_id |
string | yes |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/retention_offers/$OFFER_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Get Apple Retention Messaging settings#
GET /v2/projects/{project_id}/apps/{app_id}/retention_messaging · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/retention_messaging" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The settings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Save messages, defaults and real-time rules#
POST /v2/projects/{project_id}/apps/{app_id}/retention_messaging · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Messages follow Apple's limits (header 66, body 144 characters). Default messages must be text messages. A message already uploaded to Apple cannot change: add a new one.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | no | |
messages |
array of object | no | |
messages[].id |
string | yes | A UUID: Apple's messageIdentifier. |
messages[].kind |
text, switch_plan, promotional_offer |
yes | |
messages[].header |
string | yes | Up to 66 characters. |
messages[].body |
string | yes | Up to 144 characters. |
messages[].alternate_product_id |
string or null | no | switch_plan: the product to suggest. |
messages[].promotional_offer_id |
string or null | no | promotional_offer: the App Store promotional offer id. |
messages[].uploaded |
array of sandbox, production |
no | Environments Apple accepted the upload in. |
messages[].error |
string or null | no | |
defaults |
array of object | no | |
defaults[].product_id |
string | no | |
defaults[].locale |
string | no | |
defaults[].message_id |
string | no | |
rules |
array of object | no | |
rules[].product_id |
string or null | no | |
rules[].message_id |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/retention_messaging" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"enabled":true,"messages":[{"id":"11111111-2222-4333-8444-555555555555","kind":"text","header":"Your scans stay unlimited","body":"Keep unlimited scans and cloud backup."}],"defaults":[{"product_id":"pro_monthly","locale":"en-US","message_id":"11111111-2222-4333-8444-555555555555"}],"rules":[{"product_id":null,"message_id":"11111111-2222-4333-8444-555555555555"}]}'Responses
- 200: The settings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Upload to Apple and register the real-time URL#
POST /v2/projects/{project_id}/apps/{app_id}/retention_messaging/actions/sync · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Calls Apple's Upload Message for messages not yet in the environment, Configure Default Message for each default, and Configure Realtime URL with this server's /v1/retention/apple/{app_id}, using the app's In-App Purchase key. Apple grants access to the Retention Messaging API on request; production also needs Apple's performance test.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
environment |
sandbox, production |
no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/retention_messaging/actions/sync" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"environment":"sandbox"}'Responses
- 200: The settings and per-step errors.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Apple's real-time Get Retention Message call#
POST /v1/retention/apple/{app_id} · Auth: none · RevenueDot extension
Apple posts { signedPayload } when a customer is about to cancel. RevenueDot verifies Apple's signature and the app's Apple ID, then answers from the real-time rules: message, alternateProduct or promotionalOffer (signed with promotionalOfferSignatureV1), or {} so Apple shows the default message.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
app_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
signedPayload |
string | yes | JWS signed by the App Store. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v1/retention/apple/$APP_ID"Responses
- 200: The chosen message.
- 400: Not signed by Apple, or for another app.
- 404: Unknown App Store app.
Example 200 response:
{
"message": {
"messageIdentifier": "11111111-2222-4333-8444-555555555555"
}
}Support#
Customer Center tickets, the customer summary for help desk sidebars, and the Intercom inbox app's Canvas Kit endpoint. See Support.
List Customer Center tickets, newest first#
GET /v2/projects/{project_id}/support_tickets · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
open, closed, all |
no | |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/support_tickets" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Tickets.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Get a ticket#
GET /v2/projects/{project_id}/support_tickets/{ticket_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
ticket_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/support_tickets/$TICKET_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The ticket.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Close or reopen a ticket#
POST /v2/projects/{project_id}/support_tickets/{ticket_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
ticket_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
status |
open, closed |
yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/support_tickets/$TICKET_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"status":"closed"}'Responses
- 200: The ticket.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
What a help desk sidebar shows about a customer#
GET /v2/projects/{project_id}/customers/{customer_id}/support_summary · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
For an Intercom or Zendesk sidebar app: status, entitlements, subscriptions with auto-renew and store, total spent, refund requests, open tickets and a dashboard link.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
customer_id |
string | yes | Any app user id of the customer. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customers/user_1/support_summary" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The summary.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Look customers up by email#
GET /v2/projects/{project_id}/support_summaries · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | yes | Matched case-insensitively against the $email attribute. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/support_summaries" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Up to 10 summaries.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Intercom inbox app: the customer's subscription as Canvas Kit components#
POST /v1/support/intercom/{project_id}/canvas · Auth: none · RevenueDot extension
Set this URL as the initialize URL of an Intercom Canvas Kit app for the Inbox (Canvas Kit). Intercom signs each request; RevenueDot checks the signature with the client secret saved on the project's intercom_inbox integration. The contact is found by external_id (your app user ID), then by email. The answer is a Canvas Kit canvas: status, entitlements, plan, store, renewal or expiry date, billing issue, total spent, customer since, app user ID, country, refund requests, open tickets and an Open in RevenueDot button.
- 401: no signature, or it does not match. 404: the Intercom inbox is not connected to this project (or turned off). 413: the body is over 64,000 characters. 400: the body is not JSON. See Support.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-Body-Signature |
string | yes | Intercom's hex HMAC-SHA256 of the raw body, keyed with the Intercom app's client secret. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
contact |
object | no | The conversation's contact, as Intercom sends it. |
contact.external_id |
string | no | Your app user ID. |
contact.email |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v1/support/intercom/$PROJECT_ID/canvas" \
-H "Content-Type: application/json" -d '{"contact":{"external_id":"user_42","email":"wren@example.com"}}'Responses
- 200: Canvas Kit components.
- 400: Not JSON.
- 401: Missing or wrong signature.
- 404: Not connected.
- 413: Too large.
Example 200 response:
{
"canvas": {
"content": {
"components": [
{
"type": "text",
"text": "RevenueDot",
"style": "header"
},
{
"type": "data-table",
"items": [
{
"type": "field-value",
"field": "Status",
"value": "Active"
},
{
"type": "field-value",
"field": "Entitlements",
"value": "pro"
},
{
"type": "field-value",
"field": "Plan",
"value": "pro_monthly"
},
{
"type": "field-value",
"field": "Store",
"value": "app_store"
},
{
"type": "field-value",
"field": "Renews",
"value": "2026-10-30"
},
{
"type": "field-value",
"field": "Total spent",
"value": "$59.94"
},
{
"type": "field-value",
"field": "Customer since",
"value": "2026-04-02"
},
{
"type": "field-value",
"field": "App user ID",
"value": "user_42"
}
]
},
{
"type": "divider"
},
{
"type": "button",
"id": "open-revenuedot",
"label": "Open in RevenueDot",
"style": "secondary",
"action": {
"type": "url",
"url": "https://app.revenuedot.app/projects/proj1a2b3c4d/customers/user_42"
}
}
]
}
}
}Payment recovery#
Emails to subscribers whose renewal failed on any store, with one link to fix the payment, and the revenue that comes back. See Payment recovery.
Get payment recovery settings#
GET /v2/projects/{project_id}/payment_recovery · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/payment_recovery" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Settings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Replace payment recovery settings#
POST /v2/projects/{project_id}/payment_recovery · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Open cases move to the new schedule: each case's next email is due on its new day.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | yes | |
steps |
array of object | yes | |
steps[].day |
integer | yes | Days after the billing issue started. 0 sends right away. |
steps[].subject |
string | yes | {app} becomes the sender name. |
steps[].heading |
string | yes | |
steps[].body |
string | yes | Plain text; a blank line starts a paragraph. |
steps[].button_label |
string | yes | |
window_days |
integer | no | |
include_sandbox |
boolean | no | |
sender_name |
string or null | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/payment_recovery" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"enabled":true,"window_days":30,"include_sandbox":false,"sender_name":null,"steps":[{"day":0,"subject":"Your payment for {app} didn'\''t go through","heading":"Update your payment method","body":"We couldn'\''t charge your payment method for your {app} subscription.\n\nUpdate your payment details to keep your access.","button_label":"Update payment"},{"day":3,"subject":"Action needed: keep your {app} subscription","heading":"Your subscription is at risk","body":"Your last payment for {app} still hasn'\''t gone through.","button_label":"Update payment"}]}'Responses
- 200: Settings.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
At risk, emails sent, recovered revenue#
GET /v2/projects/{project_id}/payment_recovery/stats · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
days |
integer | no | |
environment |
production, sandbox |
no | Default production. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/payment_recovery/stats" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The numbers.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "payment_recovery_stats",
"days": 28,
"environment": "production",
"at_risk": {
"count": 3,
"revenue_in_usd": 89.97
},
"opened": 9,
"messages_sent": 14,
"clicked": 6,
"recovered": {
"count": 5,
"revenue_in_usd": 179.95
},
"recovered_without_message": {
"count": 1,
"revenue_in_usd": 9.99
},
"lost": {
"count": 1
},
"recovery_rate": 0.857,
"by_store": [
{
"store": "app_store",
"at_risk": 2,
"recovered": 4,
"recovered_revenue_in_usd": 119.96,
"recovered_without_message": 1,
"lost": 1
}
]
}List recovery cases#
GET /v2/projects/{project_id}/payment_recovery/cases · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Newest first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status |
open, recovered, lost |
no | |
environment |
production, sandbox |
no | Default production. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/payment_recovery/cases" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Cases.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Send a test recovery email#
POST /v2/projects/{project_id}/payment_recovery/actions/send_test · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Sends one step (saved, or content to preview unsaved edits) to any address with sample links. At most 10 an hour per project.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | |
step |
integer | no | 0-based, default 0. |
content |
object | no | |
content.day |
integer | yes | Days after the billing issue started. 0 sends right away. |
content.subject |
string | yes | {app} becomes the sender name. |
content.heading |
string | yes | |
content.body |
string | yes | Plain text; a blank line starts a paragraph. |
content.button_label |
string | yes | |
sender_name |
string or null | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/payment_recovery/actions/send_test" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"email":"me@example.com","step":0}'Responses
- 200: Sent.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
- 502: An upstream service (the store or the language model) gave no usable answer. Retry later. Returns V2Error.
Send the emails that are due now#
POST /v2/projects/{project_id}/payment_recovery/actions/run · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
The minute tick sends due emails too (on Cloud from the cron). Closes cases whose window passed. 422: recovery is off.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/payment_recovery/actions/run" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Counts.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Email link: opens the place to fix the payment#
GET /v1/recovery/l/{token} · Auth: none · RevenueDot extension
Records the click and redirects: Apple's payment page, the Play Store subscription page, Amazon's subscriptions page, or a Stripe customer portal session made now (the open invoice's page when the portal is not set up). Test Store purchases get a page that explains the test.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/recovery/l/$TOKEN"Responses
- 200: An explanation page.
- 303: To the store or Stripe.
- 404: Unknown link.
Customer Center link (customer info's management_url)#
GET /v1/recovery/c/{token} · Auth: none · RevenueDot extension
The Customer Center token, never the emailed one. App Store, Google Play and Amazon purchases redirect straight to the store's page. A web (Stripe) purchase gets a page that offers to email a one-time link: customer info is readable with the app's public key, so this link never opens the Stripe portal itself. Without an email address on file, the page says where to update the payment instead.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/recovery/c/$TOKEN"Responses
- 200: HTML.
- 303: To the store.
- 404: Unknown link.
Email a one-time link to the Stripe customer portal#
POST /v1/recovery/c/{token} · Auth: none · RevenueDot extension
Emails a link that works once, for 30 minutes, to the address on file (the case's email, $email, or the Stripe customer's). At most 3 an hour per customer and 10 an hour per IP address.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v1/recovery/c/$TOKEN"Responses
- 200: Check your email (or no email on file).
- 404: Unknown link.
- 429: Too many requests.
- 503: The email could not be sent.
One-time link page (a button; opening it spends nothing)#
GET /v1/recovery/p/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/recovery/p/$TOKEN"Responses
- 200: A button, or why the link no longer works (used, expired).
- 404: Unknown link.
Use the one-time link: a Stripe portal session made now#
POST /v1/recovery/p/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v1/recovery/p/$TOKEN"Responses
- 200: Why the link no longer works.
- 303: To the Stripe customer portal.
- 404: Unknown link.
Where the Stripe portal returns to#
GET /v1/recovery/done/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/recovery/done/$TOKEN"Responses
- 200: A thank-you page.
- 404: Unknown link.
Unsubscribe page (asks first)#
GET /v1/recovery/u/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/recovery/u/$TOKEN"Responses
- 200: HTML.
- 404: Unknown link.
Unsubscribe (also RFC 8058 one-click)#
POST /v1/recovery/u/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v1/recovery/u/$TOKEN"Responses
- 200: HTML confirmation.
- 404: Unknown link.
Win-back#
Campaigns that email churned subscribers an offer, with tracked links and one-click unsubscribe.
List win-back campaigns#
GET /v2/projects/{project_id}/winback_campaigns · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Campaigns with stats.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Create a win-back campaign#
POST /v2/projects/{project_id}/winback_campaigns · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | |
status |
draft, active, paused |
no | |
audience |
object | no | |
audience.churned_min_days |
integer | yes | |
audience.churned_max_days |
integer | yes | |
audience.product_ids |
array of string | no | |
audience.stores |
array of string | no | |
audience.audience_id |
string or null | no | A saved audience to narrow to. |
email |
object | yes | |
email.subject |
string | yes | |
email.heading |
string | yes | |
email.body |
string | yes | Plain text; a blank line starts a paragraph. |
email.button_label |
string | yes | |
email.sender_name |
string or null | no | Defaults to the project name. |
offer |
object | yes | |
offer.type |
store, url |
yes | store: the App Store's subscriptions page or the Play Store page of the customer's product. url: your own https link. |
offer.url |
string or null | no | |
send_hour_utc |
integer | no | |
track_opens |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"name":"Come back in September","status":"active","audience":{"churned_min_days":3,"churned_max_days":60,"product_ids":[],"stores":[],"audience_id":null},"email":{"subject":"We saved your scans","heading":"Your scans are waiting","body":"Come back to Scanner Pro.","button_label":"Resubscribe"},"offer":{"type":"store"},"send_hour_utc":16,"track_opens":false}'Responses
- 201: The campaign.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Get a campaign with stats and recent emails#
GET /v2/projects/{project_id}/winback_campaigns/{campaign_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
campaign_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns/$CAMPAIGN_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The campaign.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Update, start or pause a campaign#
POST /v2/projects/{project_id}/winback_campaigns/{campaign_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
campaign_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
status |
draft, active, paused |
no | |
audience |
object | no | |
audience.churned_min_days |
integer | yes | |
audience.churned_max_days |
integer | yes | |
audience.product_ids |
array of string | no | |
audience.stores |
array of string | no | |
audience.audience_id |
string or null | no | A saved audience to narrow to. |
email |
object | no | |
email.subject |
string | yes | |
email.heading |
string | yes | |
email.body |
string | yes | Plain text; a blank line starts a paragraph. |
email.button_label |
string | yes | |
email.sender_name |
string or null | no | Defaults to the project name. |
offer |
object | no | |
offer.type |
store, url |
yes | store: the App Store's subscriptions page or the Play Store page of the customer's product. url: your own https link. |
offer.url |
string or null | no | |
send_hour_utc |
integer | no | |
track_opens |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns/$CAMPAIGN_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"status":"paused"}'Responses
- 200: The campaign.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete a campaign#
DELETE /v2/projects/{project_id}/winback_campaigns/{campaign_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
campaign_id |
string | yes |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns/$CAMPAIGN_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Who would get the email now#
POST /v2/projects/{project_id}/winback_campaigns/{campaign_id}/actions/preview · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
campaign_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns/$CAMPAIGN_ID/actions/preview" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Count and sample.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Send a test email#
POST /v2/projects/{project_id}/winback_campaigns/{campaign_id}/actions/send_test · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
campaign_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns/$CAMPAIGN_ID/actions/send_test" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"email":"me@example.com"}'Responses
- 200: Sent.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Send now#
POST /v2/projects/{project_id}/winback_campaigns/{campaign_id}/actions/run · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Emails up to 500 eligible customers now. Each customer gets a campaign's email at most once. Active campaigns also send daily at send_hour_utc.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
campaign_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/winback_campaigns/$CAMPAIGN_ID/actions/run" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Counts.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Email button: records the click and redirects to the offer#
GET /v1/winback/c/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/winback/c/$TOKEN"Responses
- 302: To the offer.
- 404: Unknown link.
Open-tracking image (campaigns with track_opens)#
GET /v1/winback/o/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/winback/o/$TOKEN"Responses
- 200: A 1×1 GIF.
Unsubscribe page (asks first)#
GET /v1/winback/u/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v1/winback/u/$TOKEN"Responses
- 200: HTML.
- 404: Unknown link.
Unsubscribe (also RFC 8058 one-click)#
POST /v1/winback/u/{token} · Auth: none · RevenueDot extension
The URL in each win-back email's List-Unsubscribe header, sent with List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058). Mail apps POST the form body List-Unsubscribe=One-Click with no cookie or key; the token alone adds the address to the project's suppression list. Answers 200 without a redirect; repeating it changes nothing. A test email's link changes nothing.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v1/winback/u/$TOKEN"Responses
- 200: HTML confirmation.
- 404: Unknown link.
Customer lists#
Built-in customer lists, saved audiences, filters, summary cards and CSV export.
Customers in a list, with the summary cards#
GET /v2/projects/{project_id}/customer_lists · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Built-in lists or a saved audience, optionally filtered by audience rules and a search. Covers every customer of the project. Built-in lists, search and sorting are SQL; audience rules are checked a page of customers at a time. The summary is exact: a project above 5,000 customers with audience rules is counted in the background (summary.is_counting until the first count, then summary.counted_at).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
list |
string | no | all (default), active, sandbox, non_subscription, expired, or a saved audience id. |
rules |
string | no | Extra filter: audience rules as JSON. |
search |
string | no | Part of an app user id or email. |
sort |
id, subscription_status, auto_renewal_status, first_seen_at, last_seen_at, spent_in_usd |
no | Column to sort by. Default: most recently seen first, and ties keep that order. subscription_status ascending runs active, trialing, grace_period, billing_issue, expired, none; auto_renewal_status ascending runs on, off. In both directions, customers without an auto-renewal value stay last, and with sort=id anonymous IDs ($RCAnonymousID:...) come after named app user IDs. |
direction |
asc, desc |
no | Sort direction when sort is set. Default asc. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customer_lists" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Rows and the summary.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Export a list as CSV#
GET /v2/projects/{project_id}/customer_lists/export · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
list |
string | no | all (default), active, sandbox, non_subscription, expired, or a saved audience id. |
rules |
string | no | Extra filter: audience rules as JSON. |
search |
string | no | Part of an app user id or email. |
sort |
id, subscription_status, auto_renewal_status, first_seen_at, last_seen_at, spent_in_usd |
no | Column to sort by. Default: most recently seen first, and ties keep that order. subscription_status ascending runs active, trialing, grace_period, billing_issue, expired, none; auto_renewal_status ascending runs on, off. In both directions, customers without an auto-renewal value stay last, and with sort=id anonymous IDs ($RCAnonymousID:...) come after named app user IDs. |
direction |
asc, desc |
no | Sort direction when sort is set. Default asc. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customer_lists/export" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: CSV with a header row: app_user_id, email, subscription_status, auto_renewal_status, first_seen_at, last_seen_at, spent_in_usd, latest_product_id, latest_store, latest_purchase_at, country, platform.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Event log#
Every recorded event and money movement.
Event log#
GET /v2/projects/{project_id}/events · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Every event the project recorded, newest first. body is exactly what webhooks receive. Paywall events (PAYWALL_*) only with include_paywall_events=true or when type names them.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
type |
array of string | no | Event types (any case); repeat or comma-separate. Naming a paywall type returns it. |
include_paywall_events |
boolean | no | Also return paywall events (PAYWALL_*). Default false: paywall events are left out unless type names them. |
customer |
string | no | Any app user id of the customer. |
environment |
production, sandbox |
no | Only this environment. Default: both. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/events" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of results. Returns a list of Event.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Transaction feed#
GET /v2/projects/{project_id}/transactions · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:purchases:read
Every purchase, renewal, trial start, refund and refund reversal, newest first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
customer |
string | no | Any app user id of the customer. |
environment |
production, sandbox |
no | Only this environment. Default: both. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/transactions" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of results. Returns a list of Transaction.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Test Store#
Simulated purchases and lifecycles for development.
Simulate a Test Store purchase or lifecycle#
POST /v2/projects/{project_id}/test_purchases · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:purchases:read_write
Runs a purchase through the same pipeline as an SDK receipt, so events, the transaction ledger and webhooks come out as they would. Scenarios:
purchase, trial, trial_conversion, renewal, cancel, billing_issue, refund, expire. See the Test Store guide.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_user_id |
string | yes | |
product_id |
string | yes | Product id or store identifier of a Test Store product. |
app_id |
string | no | Test Store app; default the project's first. |
price |
number | no | Without a price, the product's Test Store price. |
currency |
string | no | ISO 4217 code; default USD. A code with no exchange rate to USD is refused. |
purchased_at |
integer | no | Start, epoch milliseconds. Not with offset_days. |
presented_offering_id |
string | no | |
scenario |
purchase, trial, trial_conversion, renewal, cancel, billing_issue, refund, expire |
no | |
offset_days |
number | no | Days ago the scenario starts (0 to 730). |
country_code |
string | no | ISO 3166-1 alpha-2, upper case. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/test_purchases" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"app_user_id":"user_renewal","product_id":"pro_monthly","scenario":"renewal","price":9.99}'Responses
- 201: What happened. Returns TestPurchase.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Dashboard data#
Series and rows the dashboard shows.
Daily history of an overview metric#
GET /v2/projects/{project_id}/metrics/history · Auth: secret key or dashboard session · RevenueDot extension · Permissions: charts_metrics:overview:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
metric |
active_trials, active_subscriptions, mrr, revenue, new_customers, active_users |
yes | |
days |
integer | no | |
environment |
production, sandbox |
no |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/metrics/history" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The history. Returns MetricHistory.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Dashboard rows for customers#
GET /v2/projects/{project_id}/customer_summaries · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Revenue, entitlement names and prices per customer. Unknown ids are left out.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
ids |
string | yes | Up to 100 app user ids, comma separated or repeated. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customer_summaries" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of results. Returns a list of CustomerSummary.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Overview cards summed across your projects#
GET /v2/overview · Auth: dashboard session · RevenueDot extension · Permissions: charts_metrics:overview:read
The six Overview cards (active trials, active subscriptions, MRR, revenue, new customers, active customers) summed over every project the signed-in user belongs to, as on the dashboard's Overview with "All projects" selected. Each card has value (the per-project Overview definition) and history (the daily series for days, summed per date).
Each project is checked like a project route: a project counts only where the user's role (or an enterprise custom role) includes charts_metrics:overview:read; enforced single sign-on and an organization that deprovisioned the user leave one out. projects lists every project the user is a member of with included and, when left out, reason; a project the user was removed from is not listed at all. Dashboard sessions only: a secret key belongs to one project and gets 403.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
environment |
production, sandbox |
no | |
days |
integer | no | |
project_ids |
string | no | Only these projects (comma separated, at most 100). Ids you cannot open are ignored. |
Example request
curl -s "$REVENUEDOT_URL/v2/overview"Responses
- 200: The summed cards.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
Transactions across your projects#
GET /v2/overview/transactions · Auth: dashboard session · RevenueDot extension · Permissions: customer_information:purchases:read
Every purchase, renewal, trial start, refund and refund reversal of the projects the signed-in user may read purchases in (customer_information:purchases:read), newest first, each with project_id. The list also carries projects with included and reason, as on GET /v2/overview. starting_after must be a transaction of an included project.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
environment |
production, sandbox |
no | |
project_ids |
string | no | Only these projects (comma separated, at most 100). Ids you cannot open are ignored. |
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/overview/transactions"Responses
- 200: A page of results. Returns a list of Transaction.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
Data moves#
Full exports of a project and moves between RevenueDot servers (self-hosted and Cloud) that keep ids, SDK keys, secret keys and webhook secrets: export jobs and downloads, move states (paused, forwarded), the dashboard's server-run move, and the target side with an import token. See Move projects and export everything.
List the project's exports#
GET /v2/projects/{project_id}/exports · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/exports" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Exports, newest first.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Export the whole project#
POST /v2/projects/{project_id}/exports · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Starts an export job of everything the project owns (64 tables) as a versioned archive: gzip JSON Lines per table and a manifest with the schema version, row counts and checksums. The job runs in short slices in the background; poll it, or call actions/advance. Secrets are left out unless passphrase is given (12 or more characters); then they are encrypted with it (AES-256-GCM, PBKDF2-SHA-256). Admins only in the dashboard.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
purpose |
download, move |
no | |
include_secrets |
boolean | no | |
passphrase |
string | no | 12 to 200 characters. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/exports" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"include_secrets":true,"passphrase":"correct horse battery staple"}'Responses
- 202: The export, queued.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Get the latest export#
GET /v2/projects/{project_id}/export · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/export" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The export.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Get an export#
GET /v2/projects/{project_id}/exports/{export_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Once succeeded, it has the manifest and a download_url valid for an hour. Each archive file can also be read one by one with GET …/exports/{export_id}/files/{name} (manifest.json, members.json, tables/<table>/0001.jsonl.gz, secrets/<table>/0001.json.enc).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/exports/$EXPORT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The export.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete an export's files now#
DELETE /v2/projects/{project_id}/exports/{export_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/exports/$EXPORT_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Do the next slice of an export now#
POST /v2/projects/{project_id}/exports/{export_id}/actions/advance · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Works for about eight seconds and returns the export. npx revenuedot export and move drive exports with it; the server's scheduled tick also advances queued exports.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
export_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/exports/$EXPORT_ID/actions/advance" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The export.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Download an archive#
GET /v2/exports/download/{token} · Auth: none · RevenueDot extension
The whole archive as one uncompressed .tar whose members are the archive files. The token is signed and expires after an hour; it is the only auth.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/exports/download/$TOKEN"Responses
- 200: The archive.
- 404: Expired or unknown link, or the export is gone.
Get the project's move state#
GET /v2/projects/{project_id}/move · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The state.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Move the project to another server (run by this server)#
POST /v2/projects/{project_id}/move · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
The dashboard's move: this server exports the project and copies it to to_url with the target's import token, then verifies. dry_run only compares rows per table. Poll GET …/move or call move/actions/advance; then move/finish.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
to_url |
string | yes | The target server, e.g. https://api.revenuedot.app. |
to_token |
string | yes | An import token (rdi_…) from the target's Receive a project. |
dry_run |
boolean | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"to_url":"https://api.revenuedot.app","to_token":"rdi_…","dry_run":true}'Responses
- 202: The move.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: The project is already moving. Returns V2Error.
Do the next step of the move now#
POST /v2/projects/{project_id}/move/actions/advance · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move/actions/advance" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The move.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Switch to the target#
POST /v2/projects/{project_id}/move/finish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Pauses writes here, copies again, verifies, puts the project live on the target and forwards this server's traffic there. A verification failure lifts the pause.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move/finish" -H "Authorization: Bearer $SECRET_KEY"Responses
- 202: The move.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: The move is not copied yet, or it failed. Returns V2Error.
Pause writes for the last copy#
POST /v2/projects/{project_id}/move/pause · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Reads keep working. SDK writes and store notifications answer 503 with Retry-After: 60 (the SDKs and the stores retry); API v2 writes answer 423. npx revenuedot move --finish calls it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move/pause" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The state.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: The project is forwarded or incoming. Returns V2Error.
Forward the paused project to its new server#
POST /v2/projects/{project_id}/move/forward · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Every /v1, /rcbilling and secret-key /v2 request for the project, and every store notification, is passed to to_url with the same method, path, headers and body; the answer comes back unchanged with an x-revenuedot-moved-to header. A forwarding loop answers 508.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
to_url |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move/forward" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"to_url":"https://api.revenuedot.app"}'Responses
- 200: The state.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: Pause and copy the project first. Returns V2Error.
Serve the project here again#
POST /v2/projects/{project_id}/move/cancel · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Stops a running move and clears paused or forwarded. A copy already on the target stays there.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/move/cancel" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The state.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: The project is being copied in here. Returns V2Error.
Create an import token (Receive a project)#
POST /v2/imports/tokens · Auth: dashboard session · RevenueDot extension
A token (rdi_ and 64 hex characters) that lets one project move into the signed-in account on this server. Shown once; lasts 24 hours. RevenueDot Cloud needs a confirmed email address.
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/imports/tokens"Responses
- 201: The token.
- 401: Not signed in.
- 403: Confirm your email first, or the request did not come from the dashboard.
List your moves into this server#
GET /v2/imports · Auth: dashboard session · RevenueDot extension
Example request
curl -s "$REVENUEDOT_URL/v2/imports"Responses
- 200: The last 20 imports.
- 401: Not signed in.
Check or start loading an archive#
POST /v2/imports · Auth: dashboard session · RevenueDot extension
With dry_run: what loading would do (conflicts, rows per table here now against the archive); nothing is written. Without: starts the import (the project is created incoming). Then send each file with PUT /v2/imports/{import_id}/files/{name} (its SHA-256 must match the manifest; tables upsert by primary key and never change another project's rows; a row whose parent record is not here, such as an alias of a customer created on the source during the export, is left out and counted; a file already loaded answers applied: false), members.json to …/members, then …/verify and …/finish. Sending a new manifest starts the incoming copy over.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
manifest |
object | yes | The archive's manifest.json. |
passphrase |
string | no | Needed when the archive includes secrets. |
dry_run |
boolean | no | |
replace |
boolean | no | Replace a moved-away copy of the project that is still here. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/imports"Responses
- 200: The plan (dry run).
- 201: The import.
- 400: Bad manifest or passphrase.
- 401: Missing or expired import token.
- 409: A conflict with another project here, or the import is finished.
- 422: The archive was written by a newer RevenueDot.
Get an import#
GET /v2/imports/{import_id} · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
import_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/imports/$IMPORT_ID"Responses
- 200: The import.
- 401: Missing token.
- 404: Not found.
List the project's collaborators to invite#
POST /v2/imports/{import_id}/members · Auth: dashboard session · RevenueDot extension
The archive's members.json. Everyone in it is listed to invite on this server (the finish report repeats the list); nobody is added directly, even with an account here. The person who imports owns the project.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
import_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
members |
array of object | no | |
members[].email |
string | no | |
members[].role |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/imports/$IMPORT_ID/members"Responses
- 200: To invite.
- 401: Missing token.
- 409: Nothing imported yet, or the import is finished.
Verify the copy#
POST /v2/imports/{import_id}/verify · Auth: dashboard session · RevenueDot extension
Recomputes the rows and checksum of every table here and compares them with the manifest. Large projects take several calls: done: false until finished.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
import_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/imports/$IMPORT_ID/verify"Responses
- 200: Progress or the result.
- 401: Missing token.
- 409: Nothing imported yet, or verification failed.
Put the project live here#
POST /v2/imports/{import_id}/finish · Auth: dashboard session · RevenueDot extension
Every file must be loaded. Webhooks without a secret get a new one. The answer lists the store notification URLs to change and where.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
import_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/imports/$IMPORT_ID/finish"Responses
- 200: The report.
- 401: Missing token.
- 409: Files are missing, or the import is finished.
Cloud billing#
RevenueDot Cloud only: the account's plan (Pro or Enterprise), its go-live stage, tracked revenue and invoices, Stripe Checkout to start Pro and the Customer Portal on RevenueDot's own Stripe account. See Cloud billing.
The account's plan, go-live stage, usage and invoices#
GET /v2/billing · Auth: dashboard session · RevenueDot extension
RevenueDot Cloud only; a self-hosted server answers 404. Tracked revenue is the USD sum of the month's production purchases, renewals and one-time purchases that earned money in the projects the account owns; sandbox, trials, refunds and imported or moved-in history do not count. Add ?sync=1 after Stripe Checkout to read Stripe at once (at most once every 5 seconds).
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
sync |
1 |
no | 1: read the subscription from Stripe now, so a new plan shows before the webhook lands. |
Example request
curl -s "$REVENUEDOT_URL/v2/billing"Responses
- 200: Billing.
- 401: Not signed in.
- 404: Self-hosted: billing is only on RevenueDot Cloud.
Start Pro with Stripe Checkout#
POST /v2/billing/checkout · Auth: dashboard session · RevenueDot extension
A Stripe Checkout session on RevenueDot's own Stripe account: the metered Pro price, a card collected and $0 due today, billed monthly on the 1st from the 1st of next month, no proration. standard, Pro's old name, is still accepted.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
plan |
pro |
yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/billing/checkout" \
-H "Content-Type: application/json" -d '{"plan":"pro"}'Responses
- 200: Go to url.
- 400: Only Pro has a self-serve checkout; Enterprise is through sales.
- 401: Not signed in.
- 403: The request did not come from the dashboard.
- 409: Already on Pro or on Enterprise.
- 502: Stripe refused.
- 503: Billing is not set up on this server.
Open the Stripe Customer Portal#
POST /v2/billing/portal · Auth: dashboard session · RevenueDot extension
Change the card, see invoices or cancel at period end. A cancelled Pro plan ends at the period end; the account then has no plan, and a live account is paused at once.
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/billing/portal"Responses
- 200: Go to url.
- 401: Not signed in.
- 409: No payment method yet.
- 502: Stripe refused.
- 503: Billing is not set up on this server.
Webhook of RevenueDot's own Stripe account#
POST /v2/billing/stripe/webhook · Auth: none · RevenueDot extension
Stripe only, signed with REVENUEDOT_BILLING_STRIPE_WEBHOOK_SECRET: checkout.session.completed, customer.subscription.* and invoice.* events keep the plan, the payment status and the invoices.
Request body (application/json)
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/billing/stripe/webhook"Responses
- 200: Handled.
- 400: Bad signature or body.
- 503: Billing webhooks are not set up.
Migration import#
Bulk import from RevenueCat, used by the revenuedot import CLI.
Import customers with their purchases#
POST /v2/projects/{project_id}/import/customers · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read_write
Up to 100 RevenueCat-shaped customers per call, each with aliases, attributes, subscriptions and one-time purchases. Writes state directly: no events and no webhooks unless emit_events is true.
Keeps first-seen dates, original purchase dates and store transaction ids, and keys each subscription like the store adapters do (Apple original transaction id, Google purchase token), so later receipts and notifications update the imported row. Running the same import twice changes nothing.
Google subscriptions without purchase_token are keyed needs_token_refresh:<order id> until a token is found. The revenuedot import CLI calls this; see the importer.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
customers |
array of object | yes | |
customers[].id |
string | yes | |
customers[].aliases |
array of string | no | |
customers[].first_seen_at |
integer | no | |
customers[].last_seen_at |
integer | no | |
customers[].last_seen_app_version |
string or null | no | |
customers[].last_seen_country |
string or null | no | |
customers[].last_seen_platform |
string or null | no | |
customers[].attributes |
array of object | no | |
customers[].attributes[].name |
string | yes | |
customers[].attributes[].value |
string or null | yes | |
customers[].attributes[].updated_at |
integer | no | |
customers[].subscriptions |
array of object | no | |
customers[].subscriptions[].source_id |
string | no | |
customers[].subscriptions[].app_id |
string or null | no | |
customers[].subscriptions[].store |
string | yes | |
customers[].subscriptions[].product_identifier |
string | yes | The store product id. Google Play subscriptionId:basePlanId and App Store productId:monthly (iOS 26.4 billing plan) are split into product and plan; productId:upFront is the bare App Store product. |
customers[].subscriptions[].environment |
production, sandbox |
no | |
customers[].subscriptions[].ownership |
purchased, family_shared |
no | |
customers[].subscriptions[].starts_at |
integer | yes | |
customers[].subscriptions[].current_period_starts_at |
integer | yes | |
customers[].subscriptions[].current_period_ends_at |
integer or null | no | |
customers[].subscriptions[].status |
trialing, active, expired, in_grace_period, in_billing_retry, paused, unknown, incomplete |
yes | |
customers[].subscriptions[].auto_renewal_status |
will_renew, will_not_renew, will_change_product, will_pause, requires_price_increase_consent, has_already_renewed |
no | |
customers[].subscriptions[].store_subscription_identifier |
string | yes | |
customers[].subscriptions[].original_transaction_id |
string or null | no | |
customers[].subscriptions[].original_transaction_id_confirmed |
boolean | no | |
customers[].subscriptions[].purchase_token |
string or null | no | |
customers[].subscriptions[].period_type |
normal, trial, intro, promotional, prepaid |
no | |
customers[].subscriptions[].country |
string or null | no | |
customers[].subscriptions[].price |
object or null | no | |
customers[].subscriptions[].total_revenue_usd |
number or null | no | |
customers[].subscriptions[].unsubscribe_detected_at |
integer or null | no | |
customers[].subscriptions[].billing_issues_detected_at |
integer or null | no | |
customers[].subscriptions[].grace_period_expires_at |
integer or null | no | |
customers[].subscriptions[].refunded_at |
integer or null | no | |
customers[].subscriptions[].auto_resume_at |
integer or null | no | |
customers[].subscriptions[].entitlement_lookup_keys |
array of string | no | |
customers[].subscriptions[].auto_renew_product_identifier |
string or null | no | |
customers[].subscriptions[].transactions |
array of object | no | |
customers[].purchases |
array of object | no | |
customers[].purchases[].source_id |
string | no | |
customers[].purchases[].app_id |
string or null | no | |
customers[].purchases[].store |
string | yes | |
customers[].purchases[].product_identifier |
string | yes | |
customers[].purchases[].environment |
production, sandbox |
no | |
customers[].purchases[].purchased_at |
integer | yes | |
customers[].purchases[].store_purchase_identifier |
string | yes | |
customers[].purchases[].status |
owned, refunded |
no | |
customers[].purchases[].refunded_at |
integer or null | no | |
customers[].purchases[].consumable |
boolean | no | |
customers[].purchases[].price |
object or null | no | |
customers[].purchases[].revenue_usd |
number or null | no | |
customers[].purchases[].country |
string or null | no | |
emit_events |
boolean | no | Default false. |
resolve_store_ids |
boolean | no | Default true: use the app's store credentials to confirm Apple ids and find Google tokens. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/import/customers" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"customers":[{"id":"imported_1","aliases":["$RCAnonymousID:0f1e2d"],"first_seen_at":1735689600000,"attributes":[{"name":"$email","value":"ana@example.com"}],"subscriptions":[{"store":"test_store","app_id":"appvnrm0a5h","product_identifier":"pro_annual","starts_at":1735689600000,"current_period_starts_at":1767225600000,"current_period_ends_at":1798761600000,"status":"active","auto_renewal_status":"will_renew","store_subscription_identifier":"test_1767225600000_imported"}]}]}'Responses
- 200: A report per customer. Returns ImportResult.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "import_result",
"emit_events": false,
"customers": [
{
"id": "imported_1",
"status": "created",
"subscriptions": 1,
"purchases": 0,
"needs_token_refresh": 0,
"notes": []
}
]
}Keep an app's existing SDK key#
POST /v2/projects/{project_id}/import/apps/{app_id}/public_key · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Sets the app's public key to the one your shipped app binaries already send (appl_..., goog_...), so old app versions work against RevenueDot. The prefix must match the app's store.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
public_key |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/import/apps/$APP_ID/public_key" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"public_key":"appl_AbCdEfGhIjKlMnOpQrStUvWxYz"}'Responses
- 200: The key. Returns PublicApiKey.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
What still needs attention after an import#
GET /v2/projects/{project_id}/import/status · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/import/status" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Counts. Returns ImportStatus.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "import_status",
"customers": 13,
"subscriptions": 10,
"needs_token_refresh": 0,
"needs_token_refresh_by_app": {}
}Web billing#
Sell on the web through your own Stripe account: web providers and the setup checklist, the web config (checkout look, success page, deep link scheme), web products created in Stripe, web discounts with RevenueDot's extra settings, and the project's web address and custom domain. See Sell on the web with Stripe.
List web discounts with their settings#
GET /v2/projects/{project_id}/web_discounts · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:discounts:read
The same discounts as GET /discounts, with what RevenueCat's closed Discount schema has no room for: max_redemptions, expires_at, times_redeemed, a status, the label checkout shows, the codes with their use counts, and the Stripe coupon and promotion code ids.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/web_discounts" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of web discounts. Returns a list of WebDiscount.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "web_discount",
"id": "disc5k2m9q4x7a1b3c",
"identifier": "spring20",
"customer_facing_name": "Spring sale",
"duration_mode": "time_window",
"eligibility": "everyone",
"time_window": "P3M",
"disabled_at": null,
"type": "percentage",
"percentage": 20,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"label": "20% off for 3 months",
"product_identifiers": [],
"max_redemptions": 100,
"expires_at": null,
"times_redeemed": 1,
"status": "active",
"stripe": [
{
"app_id": "appstrp8k2m9q4",
"coupon_id": "Zx81kQ2p"
}
],
"codes": [
{
"code": "SPRING20",
"times_redeemed": 1,
"created_at": 1790800901115,
"stripe_promotion_codes": {
"appstrp8k2m9q4": "promo_1QxS0aKc8Hn4AbCd"
}
}
]
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/web_discounts"
}Get the Web page: providers and checklist#
GET /v2/projects/{project_id}/web · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
The project's Stripe apps as web providers, and the four steps to sell on the web, each true when done: a Stripe app with a key, a saved web config, at least one web product, and an offering with a web product in a package.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/web" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The overview.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "web_overview",
"pay_base": "https://api.revenuedot.app/pay",
"project_base": "https://api.revenuedot.app/pay/scanner",
"domain": {
"object": "web_domain",
"slug": "scanner",
"pay_base": "https://api.revenuedot.app/pay",
"default_base": "https://api.revenuedot.app/pay/scanner",
"base": "https://api.revenuedot.app/pay/scanner",
"custom_domain": null,
"status": "none",
"verified_at": null,
"checked_at": null,
"error": null,
"dns": [],
"cloud_note": "On RevenueDot Cloud the domain also needs a TLS certificate, which RevenueDot adds after verification (see the custom domains guide)."
},
"providers": [
{
"object": "web_provider",
"id": "appstrp8k2m9q4",
"name": "Scanner Web",
"type": "stripe",
"public_key": "strp_webtest123",
"key": {
"configured": true,
"mode": "test",
"kind": "restricted",
"last4": "Qa9x"
},
"web_config": true,
"created_at": 1790800901115
}
],
"checklist": {
"connect_stripe": true,
"web_config": true,
"web_products": true,
"offering": true
},
"web_products": 3,
"offerings_with_web_products": [
"ofrngm2u3h89blc"
]
}Get a Stripe app's web config#
GET /v2/projects/{project_id}/apps/{app_id}/web_config · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
The checkout look, legal links, success and cancel behaviour, and the deep link scheme for redemption links. Before the first save it answers the defaults with saved: false. 400 for an app that is not a Stripe app.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/web_config" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The web config. Returns WebConfig.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "web_config",
"app_id": "appstrp8k2m9q4",
"saved": true,
"updated_at": 1790801342625,
"app_name": "Scanner",
"logo_url": null,
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"terms_url": "https://scanner.example/terms",
"privacy_url": "https://scanner.example/privacy",
"support_email": "help@scanner.example",
"success_mode": "show_redemption",
"success_redirect_url": null,
"success_title": null,
"success_body": null,
"cancel_url": null,
"app_scheme": "scanner",
"app_store_url": "https://apps.apple.com/app/id123",
"play_store_url": "https://play.google.com/store/apps/details?id=com.example.scanner",
"redemption_link_hours": 24,
"presets": [
{
"name": "Ink",
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
}
},
{
"name": "Night",
"theme": {
"background": "#0A0A0A",
"text": "#FAFAFA",
"accent": "#F7B500",
"button_text": "#0A0A0A",
"corner_radius": 0
}
}
]
}Save a Stripe app's web config#
PUT /v2/projects/{project_id}/apps/{app_id}/web_config · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Send only what changes; the rest keeps its value. URLs must be https. success_redirect_url is required when success_mode is redirect. See Sell on the web with Stripe.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
app_name |
string | no | |
logo_url |
string or null | no | Logo. An https URL. |
theme |
FunnelTheme | no | |
terms_url |
string or null | no | Terms. An https URL. |
privacy_url |
string or null | no | Privacy policy. An https URL. |
support_email |
string or null | no | An email address. |
success_mode |
show_redemption, redirect |
no | |
success_redirect_url |
string or null | no | For redirect. An https URL. |
success_title |
string or null | no | |
success_body |
string or null | no | |
cancel_url |
string or null | no | Cancelled checkouts return here. An https URL. |
app_scheme |
string | no | Lower-case letters, digits, + . -, starting with a letter, 2-40 characters. |
app_store_url |
string or null | no | App Store page. An https URL. |
play_store_url |
string or null | no | Google Play page. An https URL. |
redemption_link_hours |
integer | no |
Example request
curl -s -X PUT "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/web_config" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"app_name":"Scanner","support_email":"help@scanner.example","terms_url":"https://scanner.example/terms","privacy_url":"https://scanner.example/privacy","app_scheme":"scanner","app_store_url":"https://apps.apple.com/app/id123","play_store_url":"https://play.google.com/store/apps/details?id=com.example.scanner"}'Responses
- 200: The saved web config. Returns WebConfig.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "web_config",
"app_id": "appstrp8k2m9q4",
"saved": true,
"updated_at": 1790801342625,
"app_name": "Scanner",
"logo_url": null,
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"terms_url": "https://scanner.example/terms",
"privacy_url": "https://scanner.example/privacy",
"support_email": "help@scanner.example",
"success_mode": "show_redemption",
"success_redirect_url": null,
"success_title": null,
"success_body": null,
"cancel_url": null,
"app_scheme": "scanner",
"app_store_url": "https://apps.apple.com/app/id123",
"play_store_url": "https://play.google.com/store/apps/details?id=com.example.scanner",
"redemption_link_hours": 24,
"presets": [
{
"name": "Ink",
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
}
},
{
"name": "Night",
"theme": {
"background": "#0A0A0A",
"text": "#FAFAFA",
"accent": "#F7B500",
"button_text": "#0A0A0A",
"corner_radius": 0
}
}
]
}List a Stripe app's web products#
GET /v2/projects/{project_id}/apps/{app_id}/web_products · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/web_products" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of web products. Returns a list of WebProduct.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "web_product",
"product": {
"object": "product",
"id": "prod1a2b3c4d5e6f7g",
"store_identifier": "price_1QxR2nKc8Hn4AbCd",
"type": "subscription",
"state": "active",
"subscription": {
"duration": "P1M",
"grace_period_duration": null,
"trial_duration": null
},
"one_time": {
"is_consumable": null
},
"created_at": 1790800901115,
"app_id": "appstrp8k2m9q4",
"display_name": "Pro monthly"
},
"stripe_product_id": "prod_RqA1b2C3d4E5f6",
"stripe_price_id": "price_1QxR2nKc8Hn4AbCd",
"price": {
"amount": 9.99,
"amount_minor": 999,
"currency": "USD"
},
"interval": "month",
"interval_count": 1,
"trial_days": 7,
"created_at": 1790800901115
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/apps/appstrp8k2m9q4/web_products"
}Create a web product in Stripe#
POST /v2/projects/{project_id}/apps/{app_id}/web_products · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:products:read_write
Creates a Stripe Product (metadata.revenuedot_project) and a Price in your Stripe account with the app's restricted key, then a RevenueDot product whose store_identifier is the price id, so purchases of that price map to it. duration is the billing period (P1W, P1M, P3M, P6M, P1Y) and is required for a subscription. trial_days is added to each checkout as trial_period_days.
To use a price you already have, send stripe_price_id instead of price and duration; RevenueDot reads its amount and period from Stripe. Its type must match (recurring for subscription), and it needs a fixed unit amount.
entitlement_ids attaches the product to those entitlements. 409 when a product for that price exists. 422 store_error when Stripe refuses: a key without write access to Products and Prices says so.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
display_name |
string | yes | Shown on the checkout and Stripe receipts. |
type |
subscription, consumable, non_consumable |
no | Default subscription. |
price |
object | no | |
price.amount |
number | yes | Major units, such as 9.99. |
price.currency |
string | yes | ISO 4217. |
duration |
P1W, P1M, P3M, P6M, P1Y |
no | |
trial_days |
integer or null | no | 1 to 730. |
entitlement_ids |
array of string | no | |
stripe_price_id |
string | no | Link this existing price instead of creating one. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/web_products" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"display_name":"Pro monthly","type":"subscription","price":{"amount":9.99,"currency":"USD"},"duration":"P1M","trial_days":7,"entitlement_ids":["entl1a2b3c4d5e"]}'Responses
- 201: The web product. Returns WebProduct.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 201 response:
{
"object": "web_product",
"product": {
"object": "product",
"id": "prod1a2b3c4d5e6f7g",
"store_identifier": "price_1QxR2nKc8Hn4AbCd",
"type": "subscription",
"state": "active",
"subscription": {
"duration": "P1M",
"grace_period_duration": null,
"trial_duration": null
},
"one_time": {
"is_consumable": null
},
"created_at": 1790800901115,
"app_id": "appstrp8k2m9q4",
"display_name": "Pro monthly"
},
"stripe_product_id": "prod_RqA1b2C3d4E5f6",
"stripe_price_id": "price_1QxR2nKc8Hn4AbCd",
"price": {
"amount": 9.99,
"amount_minor": 999,
"currency": "USD"
},
"interval": "month",
"interval_count": 1,
"trial_days": 7,
"created_at": 1790800901115
}Get the project's web address and custom domain#
GET /v2/projects/{project_id}/web_domain · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/web_domain" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The domain settings. Returns WebDomain.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "web_domain",
"slug": "scanner",
"pay_base": "https://api.revenuedot.app/pay",
"default_base": "https://api.revenuedot.app/pay/scanner",
"base": "https://api.revenuedot.app/pay/scanner",
"custom_domain": "pay.scanner.example",
"status": "pending",
"verified_at": null,
"checked_at": null,
"error": null,
"dns": [
{
"type": "CNAME",
"name": "pay.scanner.example",
"value": "api.revenuedot.app"
},
{
"type": "TXT",
"name": "_revenuedot.pay.scanner.example",
"value": "revenuedot-verify=k3m9q2x7z1c4v8b6n0p5w2e7"
}
],
"cloud_note": "On RevenueDot Cloud the domain also needs a TLS certificate, which RevenueDot adds after verification (see the custom domains guide)."
}Change the project's slug or custom domain#
PUT /v2/projects/{project_id}/web_domain · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
slug is the project's part of the default address (unique on the server; 409 when taken). custom_domain is a host you own, such as pay.yourapp.com; it starts pending with a new TXT token, and null removes it. A domain of RevenueDot or of another project is refused. See Custom domains.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | no | |
custom_domain |
string or null | no | Lower-cased. |
Example request
curl -s -X PUT "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/web_domain" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"custom_domain":"pay.scanner.example"}'Responses
- 200: The domain settings. Returns WebDomain.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "web_domain",
"slug": "scanner",
"pay_base": "https://api.revenuedot.app/pay",
"default_base": "https://api.revenuedot.app/pay/scanner",
"base": "https://api.revenuedot.app/pay/scanner",
"custom_domain": "pay.scanner.example",
"status": "pending",
"verified_at": null,
"checked_at": null,
"error": null,
"dns": [
{
"type": "CNAME",
"name": "pay.scanner.example",
"value": "api.revenuedot.app"
},
{
"type": "TXT",
"name": "_revenuedot.pay.scanner.example",
"value": "revenuedot-verify=k3m9q2x7z1c4v8b6n0p5w2e7"
}
],
"cloud_note": "On RevenueDot Cloud the domain also needs a TLS certificate, which RevenueDot adds after verification (see the custom domains guide)."
}Check the custom domain's DNS records#
POST /v2/projects/{project_id}/web_domain/actions/verify · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Reads the TXT record _revenuedot.<domain> and the domain's CNAME through DNS over HTTPS (Cloudflare's resolver). Both must match dns: then status is verified and the server answers that host with the project's pages. Otherwise failed with error saying which record is wrong and what DNS answered (found). At most 6 checks a minute (429).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/web_domain/actions/verify" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The result. Returns WebDomain.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
Example 200 response:
{
"object": "web_domain",
"slug": "scanner",
"pay_base": "https://api.revenuedot.app/pay",
"default_base": "https://api.revenuedot.app/pay/scanner",
"base": "https://pay.scanner.example",
"custom_domain": "pay.scanner.example",
"status": "verified",
"verified_at": 1790801342625,
"checked_at": 1790801342625,
"error": null,
"dns": [
{
"type": "CNAME",
"name": "pay.scanner.example",
"value": "api.revenuedot.app"
},
{
"type": "TXT",
"name": "_revenuedot.pay.scanner.example",
"value": "revenuedot-verify=k3m9q2x7z1c4v8b6n0p5w2e7"
}
],
"cloud_note": "On RevenueDot Cloud the domain also needs a TLS certificate, which RevenueDot adds after verification (see the custom domains guide).",
"found": {
"cname": [
"api.revenuedot.app."
],
"txt": [
"revenuedot-verify=k3m9q2x7z1c4v8b6n0p5w2e7"
]
}
}Stripe Connect#
Connect a Stripe app to the developer's own Stripe account through RevenueDot's Stripe Connect platform (OAuth or Account Links), without a restricted key. See Connect with Stripe.
Get a Stripe app's Connect status#
GET /v2/projects/{project_id}/apps/{app_id}/stripe_connect · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read
Whether this server offers Connect with Stripe (and why not), and the app's connection. See Connect with Stripe.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/stripe_connect" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The connection.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "stripe_connect",
"app_id": "app1a2b3c4d",
"available": true,
"unavailable_reason": null,
"modes": [
"live",
"test"
],
"status": "connected",
"method": "oauth",
"mode": "live",
"account": "acct_…9xQz",
"charges_enabled": true,
"details_submitted": true,
"connected_at": 1790900000000,
"disconnected_at": null,
"disconnect_reason": null,
"restricted_key_configured": false,
"webhook_url": "https://api.revenuedot.app/v1/notifications/stripe-connect",
"application_fee": null
}Start connecting a Stripe account#
POST /v2/projects/{project_id}/apps/{app_id}/stripe_connect/actions/start · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
oauth answers Stripe's authorize URL for an existing Stripe account; account_link creates a Standard account on RevenueDot's platform and answers its onboarding link. Open url in the browser and keep nonce there: Stripe sends the developer back to redirect_uri (the dashboard's /connect/stripe), which posts state, code and the nonce to actions/finish within 10 minutes.
- 400: a mode this server cannot connect in. 409: the app is already connected. 422: Connect is not set up on this server (
unavailable_reason), or Stripe refused to create the account.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
method |
oauth, account_link |
no | |
mode |
live, test |
no | |
email |
string or null | no | account_link: the new account's email. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/stripe_connect/actions/start" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"method":"oauth","mode":"live"}'Responses
- 200: Where to send the developer.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "stripe_connect_start",
"url": "https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_…&scope=read_write&state=proj1.app1.f3…&redirect_uri=https%3A%2F%2Fapp.revenuedot.app%2Fconnect%2Fstripe&stripe_landing=login",
"state": "proj1.app1.f3a9…",
"nonce": "8c1e…",
"redirect_uri": "https://app.revenuedot.app/connect/stripe",
"expires_in": 600
}Finish connecting (from the callback page)#
POST /v2/projects/{project_id}/apps/{app_id}/stripe_connect/actions/finish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
The state must be this app's pending one, under 10 minutes old, and is used once; the nonce must be the one actions/start gave this browser. For OAuth the code is exchanged at connect.stripe.com/oauth/token and only the account id is kept, sealed. For Account Links the account's status is read again.
- 400: an invalid, used or expired state, or a nonce from another browser. 422: Stripe refused the code.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
state |
string | yes | |
nonce |
string | yes | |
code |
string or null | no | OAuth only. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/stripe_connect/actions/finish" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"state":"proj1.app1.f3a9…","nonce":"8c1e…","code":"ac_…"}'Responses
- 200: The connection.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "stripe_connect",
"app_id": "app1a2b3c4d",
"available": true,
"unavailable_reason": null,
"modes": [
"live",
"test"
],
"status": "connected",
"method": "oauth",
"mode": "live",
"account": "acct_…9xQz",
"charges_enabled": true,
"details_submitted": true,
"connected_at": 1790900000000,
"disconnected_at": null,
"disconnect_reason": null,
"restricted_key_configured": false,
"webhook_url": "https://api.revenuedot.app/v1/notifications/stripe-connect",
"application_fee": null
}Disconnect the Stripe account#
POST /v2/projects/{project_id}/apps/{app_id}/stripe_connect/actions/disconnect · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:apps:read_write
Deauthorizes RevenueDot's platform at Stripe and forgets the account. If Stripe cannot be reached the app is still disconnected, deauthorized is false and warning says to remove RevenueDot under Connected apps in Stripe.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
app_id |
string | yes | App id (app...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/apps/$APP_ID/stripe_connect/actions/disconnect" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Disconnected.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Purchase links#
A hosted checkout page for one offering. See Purchase links.
List purchase links#
GET /v2/projects/{project_id}/purchase_links · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/purchase_links" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of purchase links. Returns a list of PurchaseLink.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "purchase_link",
"id": "plink_4k8m2q9x1z3c",
"name": "Spring sale",
"slug": "spring-sale",
"app_id": "appstrp8k2m9q4",
"offering_id": "ofrngm2u3h89blc",
"offering_lookup_key": "web",
"offering_display_name": "Go Pro on the web",
"discount_id": null,
"expires_at": null,
"disabled_at": null,
"status": "active",
"url": "https://api.revenuedot.app/pay/scanner/spring-sale",
"checkouts": 0,
"purchases": 0,
"created_at": 1790800901115
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/purchase_links"
}Create a purchase link#
POST /v2/projects/{project_id}/purchase_links · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
A checkout page for one offering at url. The slug defaults to one made from the name; it must be unique among the project's purchase links and funnels (409), 3-40 lower-case letters, digits or -, and not reserved (api, r, pay, success ...). app_id defaults to the project's first Stripe app; 422 when there is none. See Purchase links.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | |
offering_id |
string | yes | |
slug |
string | no | |
app_id |
string or null | no | A Stripe app of the project. |
discount_id |
string or null | no | A web discount applied without a code. |
expires_at |
integer or null | no | Epoch milliseconds. After it the page answers 410. |
enabled |
boolean | no | False creates it turned off. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/purchase_links" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"name":"Spring sale","offering_id":"ofrngm2u3h89blc","slug":"spring-sale"}'Responses
- 201: The purchase link. Returns PurchaseLink.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 201 response:
{
"object": "purchase_link",
"id": "plink_4k8m2q9x1z3c",
"name": "Spring sale",
"slug": "spring-sale",
"app_id": "appstrp8k2m9q4",
"offering_id": "ofrngm2u3h89blc",
"offering_lookup_key": "web",
"offering_display_name": "Go Pro on the web",
"discount_id": null,
"expires_at": null,
"disabled_at": null,
"status": "active",
"url": "https://api.revenuedot.app/pay/scanner/spring-sale",
"checkouts": 0,
"purchases": 0,
"created_at": 1790800901115
}Get a purchase link#
GET /v2/projects/{project_id}/purchase_links/{link_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
link_id |
string | yes | Purchase link id (plink_...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/purchase_links/$LINK_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The purchase link. Returns PurchaseLink.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "purchase_link",
"id": "plink_4k8m2q9x1z3c",
"name": "Spring sale",
"slug": "spring-sale",
"app_id": "appstrp8k2m9q4",
"offering_id": "ofrngm2u3h89blc",
"offering_lookup_key": "web",
"offering_display_name": "Go Pro on the web",
"discount_id": null,
"expires_at": null,
"disabled_at": null,
"status": "active",
"url": "https://api.revenuedot.app/pay/scanner/spring-sale",
"checkouts": 12,
"purchases": 5,
"created_at": 1790800901115
}Update a purchase link#
PATCH /v2/projects/{project_id}/purchase_links/{link_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
Send only what changes. enabled: false turns it off (410 on the page); discount_id: null removes the automatic discount; expires_at: null removes the expiry. Changing slug changes the URL; the old URL stops working.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
link_id |
string | yes | Purchase link id (plink_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
offering_id |
string | no | |
slug |
string | no | |
app_id |
string or null | no | |
discount_id |
string or null | no | |
expires_at |
integer or null | no | |
enabled |
boolean | no |
Example request
curl -s -X PATCH "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/purchase_links/$LINK_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"discount_id":"disc8h3n1v6w0z2y4d"}'Responses
- 200: The purchase link. Returns PurchaseLink.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "purchase_link",
"id": "plink_4k8m2q9x1z3c",
"name": "Spring sale",
"slug": "spring-sale",
"app_id": "appstrp8k2m9q4",
"offering_id": "ofrngm2u3h89blc",
"offering_lookup_key": "web",
"offering_display_name": "Go Pro on the web",
"discount_id": "disc8h3n1v6w0z2y4d",
"expires_at": null,
"disabled_at": null,
"status": "active",
"url": "https://api.revenuedot.app/pay/scanner/spring-sale",
"checkouts": 0,
"purchases": 0,
"created_at": 1790800901115
}Delete a purchase link#
DELETE /v2/projects/{project_id}/purchase_links/{link_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
link_id |
string | yes | Purchase link id (plink_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/purchase_links/$LINK_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "purchase_link",
"id": "plink_4k8m2q9x1z3c",
"deleted_at": 1790801342625
}Funnels#
Multi-step web-to-app funnels: quiz, info, email, paywall and success steps, publishing, analytics and Build with AI. See Funnels.
List funnels#
GET /v2/projects/{project_id}/funnels · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
Without draft and problems; with views_30d and purchases_30d.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Page size. Values outside 1-100 are clamped, not rejected. |
starting_after |
string | no | Id of the last item of the previous page. Use next_page instead of building it. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: A page of funnels. Returns a list of Funnel.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "list",
"items": [
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"name": "Focus quiz",
"slug": "focus-quiz",
"app_id": "appstrp8k2m9q4",
"status": "published",
"has_unpublished_changes": false,
"url": "https://api.revenuedot.app/pay/scanner/focus-quiz",
"published_at": 1790801342625,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"steps": 5,
"views_30d": 2,
"purchases_30d": 1
}
],
"next_page": null,
"url": "/v2/projects/proj18pzzkao/funnels"
}Create a funnel#
POST /v2/projects/{project_id}/funnels · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
Starts from draft when sent, else from a template: starter (default: a question, an info step, an email step, the paywall and the success step) or blank (the paywall and the success step). Templates take the colours of the Stripe app's web config. The draft is validated (400 names the first problem). Slugs follow the purchase link rules. See Funnels.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | |
slug |
string | no | |
app_id |
string or null | no | A Stripe app. Default: the project's first. |
template |
starter, blank |
no | |
draft |
FunnelDoc | no | A funnel: a theme and 1 to 30 steps. To publish, it needs exactly one paywall step and exactly one success step, the paywall before the success step, and the success step last. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"name":"Focus quiz"}'Responses
- 201: The funnel, as a draft. Returns Funnel.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 201 response:
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"name": "Focus quiz",
"slug": "focus-quiz",
"app_id": "appstrp8k2m9q4",
"status": "draft",
"has_unpublished_changes": false,
"url": "https://api.revenuedot.app/pay/scanner/focus-quiz",
"published_at": null,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"steps": 5,
"draft": {
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"subtitle": "Pick one. We will set up your plan around it.",
"attribute": "goal",
"options": [
{
"id": "focus",
"label": "Focus on deep work"
},
{
"id": "habits",
"label": "Build better habits"
},
{
"id": "sleep",
"label": "Sleep better"
}
]
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"subtitle": null,
"body": "People with the same goal kept going 3 times longer with a daily plan.",
"button_label": "Continue"
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"subtitle": "We will email your plan and your receipt.",
"placeholder": "you@example.com",
"required": true
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"subtitle": "Cancel anytime.",
"offering": "web",
"features": [
"Your personal daily plan",
"Reminders that adapt to you",
"Progress on every device"
],
"highlight_package": "$rc_annual",
"allow_codes": true,
"button_label": "Continue"
},
{
"id": "success",
"type": "success",
"title": "You are in",
"subtitle": null,
"body": "Open the app to start your plan. Your purchase is waiting there.",
"show_redemption": true
}
]
},
"problems": []
}Is Build with AI available?#
GET /v2/projects/{project_id}/funnels/ai · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/ai" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The language model, if any.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "funnel_ai",
"available": true,
"provider": "OpenAI",
"model": "gpt-4.1-mini",
"max_prompt_length": 1000
}Build a funnel with AI#
POST /v2/projects/{project_id}/funnels/generate · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
Asks the paywall generator's language model for a funnel and returns a draft that validates for publishing, with what was repaired in fixes. Nothing is saved: create or update a funnel with the draft. Same caps as paywall generation, shared with it: one call every 5 seconds and 60 a day per project, 100 a day per person, 5,000 a day per server (429). 503 when no model is configured; 502 when the model's answer is unusable.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
prompt |
string | yes | |
app_name |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/generate" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"prompt":"A sleep app quiz for people who wake up at night","app_name":"Drift"}'Responses
- 200: A draft.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
- 502: An upstream service (the store or the language model) gave no usable answer. Retry later. Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Example 200 response:
{
"object": "funnel_generation",
"draft": {
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"subtitle": "Pick one. We will set up your plan around it.",
"attribute": "goal",
"options": [
{
"id": "focus",
"label": "Focus on deep work"
},
{
"id": "habits",
"label": "Build better habits"
},
{
"id": "sleep",
"label": "Sleep better"
}
]
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"subtitle": null,
"body": "People with the same goal kept going 3 times longer with a daily plan.",
"button_label": "Continue"
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"subtitle": "We will email your plan and your receipt.",
"placeholder": "you@example.com",
"required": true
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"subtitle": "Cancel anytime.",
"offering": "web",
"features": [
"Your personal daily plan",
"Reminders that adapt to you",
"Progress on every device"
],
"highlight_package": "$rc_annual",
"allow_codes": true,
"button_label": "Continue"
},
{
"id": "success",
"type": "success",
"title": "You are in",
"subtitle": null,
"body": "Open the app to start your plan. Your purchase is waiting there.",
"show_redemption": true
}
]
},
"fixes": [
"added a success step"
],
"provider": "OpenAI",
"model": "gpt-4.1-mini"
}Get a funnel#
GET /v2/projects/{project_id}/funnels/{funnel_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The funnel. Returns Funnel.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"name": "Focus quiz",
"slug": "focus-quiz",
"app_id": "appstrp8k2m9q4",
"status": "published",
"has_unpublished_changes": false,
"url": "https://api.revenuedot.app/pay/scanner/focus-quiz",
"published_at": 1790801342625,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"steps": 5,
"draft": {
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"subtitle": "Pick one. We will set up your plan around it.",
"attribute": "goal",
"options": [
{
"id": "focus",
"label": "Focus on deep work"
},
{
"id": "habits",
"label": "Build better habits"
},
{
"id": "sleep",
"label": "Sleep better"
}
]
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"subtitle": null,
"body": "People with the same goal kept going 3 times longer with a daily plan.",
"button_label": "Continue"
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"subtitle": "We will email your plan and your receipt.",
"placeholder": "you@example.com",
"required": true
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"subtitle": "Cancel anytime.",
"offering": "web",
"features": [
"Your personal daily plan",
"Reminders that adapt to you",
"Progress on every device"
],
"highlight_package": "$rc_annual",
"allow_codes": true,
"button_label": "Continue"
},
{
"id": "success",
"type": "success",
"title": "You are in",
"subtitle": null,
"body": "Open the app to start your plan. Your purchase is waiting there.",
"show_redemption": true
}
]
},
"problems": []
}Update a funnel's draft#
PATCH /v2/projects/{project_id}/funnels/{funnel_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
Changes the draft, name, slug or Stripe app. Visitors keep seeing the published copy until you publish again. problems lists what still blocks publishing.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
slug |
string | no | |
app_id |
string or null | no | |
draft |
FunnelDoc | no | A funnel: a theme and 1 to 30 steps. To publish, it needs exactly one paywall step and exactly one success step, the paywall before the success step, and the success step last. |
Example request
curl -s -X PATCH "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"draft":{"theme":{"background":"#FFFFFF","text":"#0A0A0A","accent":"#0A0A0A","button_text":"#FFFFFF","corner_radius":0},"steps":[{"id":"goal","type":"question","title":"What do you want to get done?","subtitle":"Pick one. We will set up your plan around it.","attribute":"goal","options":[{"id":"focus","label":"Focus on deep work"},{"id":"habits","label":"Build better habits"},{"id":"sleep","label":"Sleep better"}]},{"id":"plan","type":"info","title":"Your plan is ready","subtitle":null,"body":"People with the same goal kept going 3 times longer with a daily plan.","button_label":"Continue"},{"id":"email","type":"email","title":"Where should we send your plan?","subtitle":"We will email your plan and your receipt.","placeholder":"you@example.com","required":true},{"id":"paywall","type":"paywall","title":"Unlock your full plan","subtitle":"Cancel anytime.","offering":"web","features":["Your personal daily plan","Reminders that adapt to you","Progress on every device"],"highlight_package":"$rc_annual","allow_codes":true,"button_label":"Continue"},{"id":"success","type":"success","title":"You are in","subtitle":null,"body":"Open the app to start your plan. Your purchase is waiting there.","show_redemption":true}]}}'Responses
- 200: The funnel. Returns Funnel.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
Example 200 response:
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"name": "Focus quiz",
"slug": "focus-quiz",
"app_id": "appstrp8k2m9q4",
"status": "published",
"has_unpublished_changes": true,
"url": "https://api.revenuedot.app/pay/scanner/focus-quiz",
"published_at": 1790801342625,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"steps": 5,
"draft": {
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"subtitle": "Pick one. We will set up your plan around it.",
"attribute": "goal",
"options": [
{
"id": "focus",
"label": "Focus on deep work"
},
{
"id": "habits",
"label": "Build better habits"
},
{
"id": "sleep",
"label": "Sleep better"
}
]
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"subtitle": null,
"body": "People with the same goal kept going 3 times longer with a daily plan.",
"button_label": "Continue"
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"subtitle": "We will email your plan and your receipt.",
"placeholder": "you@example.com",
"required": true
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"subtitle": "Cancel anytime.",
"offering": "web",
"features": [
"Your personal daily plan",
"Reminders that adapt to you",
"Progress on every device"
],
"highlight_package": "$rc_annual",
"allow_codes": true,
"button_label": "Continue"
},
{
"id": "success",
"type": "success",
"title": "You are in",
"subtitle": null,
"body": "Open the app to start your plan. Your purchase is waiting there.",
"show_redemption": true
}
]
},
"problems": []
}Delete a funnel#
DELETE /v2/projects/{project_id}/funnels/{funnel_id} · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Deleted. Returns Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"deleted_at": 1790801342625
}Publish a funnel#
POST /v2/projects/{project_id}/funnels/{funnel_id}/actions/publish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
The draft becomes the public page at url. 422 names the first problem (no paywall step, the success step not last ...), or says to connect Stripe first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID/actions/publish" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The funnel. Returns Funnel.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 422: The request is valid but cannot be done in this state or for this store. Returns V2Error.
Example 200 response:
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"name": "Focus quiz",
"slug": "focus-quiz",
"app_id": "appstrp8k2m9q4",
"status": "published",
"has_unpublished_changes": false,
"url": "https://api.revenuedot.app/pay/scanner/focus-quiz",
"published_at": 1790801342625,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"steps": 5,
"draft": {
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"subtitle": "Pick one. We will set up your plan around it.",
"attribute": "goal",
"options": [
{
"id": "focus",
"label": "Focus on deep work"
},
{
"id": "habits",
"label": "Build better habits"
},
{
"id": "sleep",
"label": "Sleep better"
}
]
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"subtitle": null,
"body": "People with the same goal kept going 3 times longer with a daily plan.",
"button_label": "Continue"
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"subtitle": "We will email your plan and your receipt.",
"placeholder": "you@example.com",
"required": true
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"subtitle": "Cancel anytime.",
"offering": "web",
"features": [
"Your personal daily plan",
"Reminders that adapt to you",
"Progress on every device"
],
"highlight_package": "$rc_annual",
"allow_codes": true,
"button_label": "Continue"
},
{
"id": "success",
"type": "success",
"title": "You are in",
"subtitle": null,
"body": "Open the app to start your plan. Your purchase is waiting there.",
"show_redemption": true
}
]
},
"problems": []
}Unpublish a funnel#
POST /v2/projects/{project_id}/funnels/{funnel_id}/actions/unpublish · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read_write
The page answers 404. The draft is kept.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID/actions/unpublish" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The funnel. Returns Funnel.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "funnel",
"id": "fnl_7q2k9m4x1z8c",
"name": "Focus quiz",
"slug": "focus-quiz",
"app_id": "appstrp8k2m9q4",
"status": "draft",
"has_unpublished_changes": false,
"url": "https://api.revenuedot.app/pay/scanner/focus-quiz",
"published_at": null,
"created_at": 1790800901115,
"updated_at": 1790801342625,
"steps": 5,
"draft": {
"theme": {
"background": "#FFFFFF",
"text": "#0A0A0A",
"accent": "#0A0A0A",
"button_text": "#FFFFFF",
"corner_radius": 0
},
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"subtitle": "Pick one. We will set up your plan around it.",
"attribute": "goal",
"options": [
{
"id": "focus",
"label": "Focus on deep work"
},
{
"id": "habits",
"label": "Build better habits"
},
{
"id": "sleep",
"label": "Sleep better"
}
]
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"subtitle": null,
"body": "People with the same goal kept going 3 times longer with a daily plan.",
"button_label": "Continue"
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"subtitle": "We will email your plan and your receipt.",
"placeholder": "you@example.com",
"required": true
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"subtitle": "Cancel anytime.",
"offering": "web",
"features": [
"Your personal daily plan",
"Reminders that adapt to you",
"Progress on every device"
],
"highlight_package": "$rc_annual",
"allow_codes": true,
"button_label": "Continue"
},
{
"id": "success",
"type": "success",
"title": "You are in",
"subtitle": null,
"body": "Open the app to start your plan. Your purchase is waiting there.",
"show_redemption": true
}
]
},
"problems": []
}Get a funnel's analytics#
GET /v2/projects/{project_id}/funnels/{funnel_id}/analytics · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
Visitors are counted once per page session. viewed and completed per step are sessions; the success step's completed is purchases. drop_off is 1 - completed / viewed. conversion is purchases / views. revenue_usd is what the purchases paid.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
days |
integer | no | The last N days. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID/analytics" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The analytics.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Example 200 response:
{
"object": "funnel_analytics",
"funnel_id": "fnl_7q2k9m4x1z8c",
"days": 7,
"views": 2,
"checkouts": 1,
"purchases": 1,
"conversion": 0.5,
"revenue_usd": 59.99,
"steps": [
{
"id": "goal",
"type": "question",
"title": "What do you want to get done?",
"viewed": 2,
"completed": 1,
"drop_off": 0.5
},
{
"id": "plan",
"type": "info",
"title": "Your plan is ready",
"viewed": 1,
"completed": 1,
"drop_off": 0
},
{
"id": "email",
"type": "email",
"title": "Where should we send your plan?",
"viewed": 1,
"completed": 1,
"drop_off": 0
},
{
"id": "paywall",
"type": "paywall",
"title": "Unlock your full plan",
"viewed": 1,
"completed": 0,
"drop_off": 1
},
{
"id": "success",
"type": "success",
"title": "You are in",
"viewed": 0,
"completed": 1,
"drop_off": 0
}
],
"daily": [
{
"date": "2026-09-30",
"views": 2,
"purchases": 1
}
]
}Get what the builder's preview needs#
GET /v2/projects/{project_id}/funnels/{funnel_id}/preview_data · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:offerings:read
The web config's look, each offering's web packages with their price labels (keyed by offering lookup key; "" is the current offering), the colour presets, the offerings and the discounts.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
funnel_id |
string | yes | Funnel id (fnl_...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/funnels/$FUNNEL_ID/preview_data" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Preview data.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Hosted pages#
The public pages RevenueDot serves for purchase links, funnels and redemption links, and the three calls those pages make. No API key. They live under /pay on the API host, at the root of REVENUEDOT_PAY_URL when it is a host of its own, and at the root of a verified custom domain.
A purchase link or a published funnel#
GET /pay/{project}/{slug} · Auth: none
Any utm_* query parameter (such as utm_source) is kept with the funnel's events. A server-rendered HTML page with a small inline script (strict Content-Security-Policy with a nonce, no-store). The same page answers at <REVENUEDOT_PAY_URL>/<project>/<slug> and, on a verified custom domain, at https://<domain>/<slug>. 404 when the page does not exist or the funnel is not published; 410 when the purchase link expired or is turned off.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project |
string | yes | The project's slug. |
slug |
string | yes | The link's or funnel's slug. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
app_user_id |
string | no | Buy for this app user id: the purchase goes straight to that customer and no redemption link is needed. |
email |
string | no | Pre-fills the email (sent to Stripe as customer_email). |
code |
string | no | Pre-fills a discount code. |
Example request
curl -s "$REVENUEDOT_URL/pay/$PROJECT/$SLUG"Responses
- 200: The page.
- 404: Not found.
- 410: The link expired or is turned off.
The success page after Stripe Checkout#
GET /pay/{project}/{slug}/success · Auth: none
Stripe sends the buyer here. The page records the purchase if the checkout.session.completed webhook has not already (either order records it once), then shows the success step: for an anonymous buyer the redemption link and store buttons, for a buyer with an app user id "Return to the app". While Stripe has not confirmed the payment it says "Your payment is processing" and reloads. With the web config's success_mode: redirect it answers 303 to success_redirect_url with redemption_url added.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project |
string | yes | |
slug |
string | yes |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
co |
string | yes | The web checkout id (wco_...). |
session_id |
string | yes | Stripe's Checkout Session id, filled in by Stripe. |
Example request
curl -s "$REVENUEDOT_URL/pay/$PROJECT/$SLUG/success"Responses
- 200: The success page.
- 303: Redirect to the web config's success URL.
- 404: Unknown checkout, or a session id that is not this checkout's.
- 410: The Checkout Session expired; nothing was charged.
The success page of the iOS SDK's hosted checkout#
GET /pay/{project}/_/success · Auth: none
Where a checkout from POST /rcbilling/v1/hosted-checkout returns. It records the purchase for the SDK's app user id; the SDK closes the page when it reaches this URL.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project |
string | yes |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
co |
string | yes | |
session_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/pay/$PROJECT/_/success"Responses
- 200: Purchase complete.
- 202: Payment processing.
- 404: Unknown checkout.
The cancel page of the iOS SDK's hosted checkout#
GET /pay/{project}/_/cancel · Auth: none
Nothing was charged. The SDK closes the page when it reaches this URL.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/pay/$PROJECT/_/cancel"Responses
- 200: Checkout cancelled.
A redemption link page#
GET /pay/r/{token} · Auth: none
The https form of a redemption link, for emails and QR codes. It shows "Open <app_scheme>://redeem_web_purchase?redemption_token=<token> and the store buttons. A redeemed token shows "Already unlocked". See Redemption links.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The redemption token (rdrt_...). |
Example request
curl -s "$REVENUEDOT_URL/pay/r/$TOKEN"Responses
- 200: The page.
- 404: Unknown token.
Start a checkout from a page#
POST /pay/api/checkout · Auth: none
What the page's script calls. It checks the plan and the discount, records a web checkout and creates a Stripe Checkout Session with the Stripe app's key (mode subscription or payment, the package's price, the trial, customer_email, the discount, and app_user_id, rd_checkout and rd_source in metadata). Without app_user_id the buyer gets a new anonymous id ($RCAnonymousID:...). Funnel answers to questions with an attribute become customer attributes once paid. 30 a minute per IP address.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
project |
string | yes | The project's slug. |
slug |
string | yes | The page's slug. |
package |
string | yes | Package lookup key, such as $rc_monthly. |
app_user_id |
string | no | |
email |
string | no | |
code |
string | no | A discount code. |
session |
string | no | Funnels: the page session id. |
answers |
object | no | Funnels: answers by step id. |
query |
object | no | The page's utm_* parameters. |
Example request
curl -s -X POST "$REVENUEDOT_URL/pay/api/checkout" \
-H "Content-Type: application/json" -d '{"project":"scanner","slug":"spring-sale","package":"$rc_monthly","code":"SPRING20"}'Responses
- 200: Redirect the buyer to
url. - 400: No plan, a plan not sold on the web, or a refused code.
- 404: The page does not exist.
- 410: The purchase link expired.
- 429: Too many attempts.
- 503: Stripe is unavailable or refused the key.
Example 200 response:
{
"url": "https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4",
"checkout_id": "wco_8k2m9q4x7a1b3c5d"
}Check a discount code from a page#
POST /pay/api/discount · Auth: none
The live check behind the code field. A refused code answers 200 with valid: false and the reason. 30 a minute per IP address.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
project |
string | yes | |
slug |
string | yes | |
package |
string | no | |
code |
string | yes | |
app_user_id |
string | no | Eligibility uses this customer's purchases. |
Example request
curl -s -X POST "$REVENUEDOT_URL/pay/api/discount" \
-H "Content-Type: application/json" -d '{"project":"scanner","slug":"spring-sale","package":"$rc_monthly","code":"spring20"}'Responses
- 200: The result.
- 400: No code, or no plan.
- 429: Too many attempts.
Example 200 response:
{
"valid": true,
"message": "20% off for 3 months applied at checkout."
}Record a funnel event from a page#
POST /pay/api/events · Auth: none
funnel_viewed, step_viewed and step_completed, sent by the page with sendBeacon. Anything else, an unknown or unpublished funnel or an unknown step is ignored; the answer is 204 either way. An email step's answer is stored as provided, never the address. The server records checkout_started and purchase itself. 300 a minute per IP address.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
type |
funnel_viewed, step_viewed, step_completed |
yes | |
funnel_id |
string | yes | |
session_id |
string | yes | 8-80 letters, digits, _ or -. |
step_id |
string | no | Not for funnel_viewed. |
answer |
string or array of string | no | step_completed of a question. |
app_user_id |
string | no | |
query |
object | no | The page URL's query: its utm_* parameters and ad click ids (fbclid, gclid, gbraid, wbraid, ttclid, msclkid) are kept with the event. |
page_url |
string | no | The page's address without its query (http or https). Kept only while an integration asks for funnel events, for ad networks; the request's IP address and user agent only while that integration is Meta or Branch (deleted after 7 days). Nothing of this with Sec-GPC: 1. |
Example request
curl -s -X POST "$REVENUEDOT_URL/pay/api/events" \
-H "Content-Type: application/json" -d '{"type":"step_completed","funnel_id":"fnl_7q2k9m4x1z8c","session_id":"3f9c2a7b1e8d4c6a9b0f1e2d3c4b5a69","step_id":"goal","answer":"Sleep better","query":{"utm_source":"tiktok","fbclid":"IwAR2xQ9kM"},"page_url":"https://api.revenuedot.app/pay/scanner/sleep-quiz"}'Responses
- 204: Accepted or ignored.
- 429: Too many events from this address.
RevenueDot AI#
The in-app assistant: conversations, streaming answers, attachments (screenshots and .storekit files), @ mentions, what the assistant may do in the project, and the first-sale card. See RevenueDot AI.
What RevenueDot AI can do here#
GET /v2/projects/{project_id}/ai · Auth: secret key or dashboard session · RevenueDot extension
The model, the runtime, the project's AI setting, the caller's role and what follows from them, and today's usage against the caps.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Status.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Set what RevenueDot AI may do in the project#
POST /v2/projects/{project_id}/ai/settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
read_write: reads, and writes after the user approves each one in the chat. read_only: no write tools. disabled: no assistant. Dashboard users must be admins.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
access |
read_write, read_only, disabled |
yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/settings" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"access":"read_only"}'Responses
- 200: Saved.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
List your conversations, newest first#
GET /v2/projects/{project_id}/ai/conversations · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q |
string | no |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations"Responses
- 200: Conversations.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Start a conversation#
POST /v2/projects/{project_id}/ai/conversations · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
title |
string or null | no | Optional; the first question becomes the title otherwise. |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations" \
-H "Content-Type: application/json" -d '{}'Responses
- 201: The conversation.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Get a conversation with its messages#
GET /v2/projects/{project_id}/ai/conversations/{conversation_id} · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
conversation_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations/$CONVERSATION_ID"Responses
- 200: The conversation.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Rename a conversation#
POST /v2/projects/{project_id}/ai/conversations/{conversation_id} · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
conversation_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations/$CONVERSATION_ID" \
-H "Content-Type: application/json" -d '{"title":"September churn"}'Responses
- 200: The conversation.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Delete a conversation#
DELETE /v2/projects/{project_id}/ai/conversations/{conversation_id} · Auth: dashboard session · RevenueDot extension
Deletes the conversation and its messages (on Cloud, its Durable Object). Changes the assistant made stay, and stay in the audit log.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
conversation_id |
string | yes |
Example request
curl -s -X DELETE "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations/$CONVERSATION_ID"Responses
- 200: Deleted.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Send a message and stream the answer (self-host)#
POST /v2/projects/{project_id}/ai/conversations/{conversation_id}/chat · Auth: dashboard session · RevenueDot extension
Self-host runtime. Send a user message, or the last assistant message back with approval decisions on its tool-<name> parts (approval: { id, approved }): only the decisions are taken, everything else comes from the stored transcript.
trigger: regenerate-message drops the last answer and asks again. Write tools always stop at tool-approval-request until the user approves, and each approval runs its write once: sending it again, or from two tabs at once, does not repeat the change. A refused turn (caps, AI setting) answers a stream with one error chunk.
Writes with the session cookie must come from the dashboard's own origin; a request a browser marks Sec-Fetch-Site: cross-site or same-site is refused with 403.
On RevenueDot Cloud conversations run in Durable Objects instead: connect a WebSocket to /agents/assistant-agent/{conversation_id} (Cloudflare Agents useAgentChat).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
conversation_id |
string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
trigger |
submit-message, regenerate-message |
no | |
messageId |
string | no | |
message |
object | no | |
message.id |
string | yes | |
message.role |
user, assistant |
yes | |
message.parts |
array of object | yes | |
message.metadata |
object | no | |
message.metadata.mentions |
array of object | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations/$CONVERSATION_ID/chat" \
-H "Content-Type: application/json" -d '{"trigger":"submit-message","message":{"id":"u1","role":"user","parts":[{"type":"text","text":"How is revenue doing this month?"}],"metadata":{"mentions":[{"type":"chart","id":"mrr","label":"MRR"}]}}}'Responses
- 200: The answer, streamed.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 423: An answer is still being written in this conversation. Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Resume the answer being written#
GET /v2/projects/{project_id}/ai/conversations/{conversation_id}/stream · Auth: dashboard session · RevenueDot extension
Replays the answer's chunks stored so far, then follows it to the end. A stream with no new chunk for 60 seconds is marked interrupted.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
conversation_id |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations/$CONVERSATION_ID/stream"Responses
- 200: The answer from its first chunk.
- 204: Nothing is being written.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Stop the answer being written#
POST /v2/projects/{project_id}/ai/conversations/{conversation_id}/stop · Auth: dashboard session · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
conversation_id |
string | yes |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/conversations/$CONVERSATION_ID/stop"Responses
- 200: Stopped.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Attach an image or a .storekit file#
POST /v2/projects/{project_id}/ai/files · Auth: dashboard session · RevenueDot extension
The raw file is the body. PNG, JPEG, WebP or GIF up to 5 MB (Content-Type names the type and the bytes must be that type), or a .storekit file up to 1 MB (at most 500 products are read). 60 uploads an hour per person. Reference it in a message as a file part with the returned url; at most 4 files per message.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
name |
string | no |
Request body (image/png)
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/files"Responses
- 201: The file.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
Read an attachment back#
GET /v2/projects/{project_id}/ai/files/{file_id} · Auth: dashboard session · RevenueDot extension
Any member of the project (and the assistant, for an attached .storekit file). Secret API keys get 403.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
file_id |
string | yes |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
format |
text, storekit |
no |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/files/$FILE_ID"Responses
- 200: The image bytes, or JSON for
format. - 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Read a .storekit file#
POST /v2/projects/{project_id}/ai/storekit · Auth: dashboard session · RevenueDot extension
Parses a StoreKit configuration file (Xcode's JSON, format versions 1 to 4) and returns its products without saving anything.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/storekit"Responses
- 200: What the file holds.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Suggestions for @ mentions#
GET /v2/projects/{project_id}/ai/mentions · Auth: dashboard session · RevenueDot extension
Up to five customers (app user ids starting with q), offerings and charts, each limited to what the caller's role can read.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
q |
string | no |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/mentions"Responses
- 200: Suggestions.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
The project's first-sale card#
GET /v2/projects/{project_id}/ai/first_sale · Auth: secret key or dashboard session · RevenueDot extension
Made when the project's first paid production purchase arrives (within 14 days of it; older projects get none). The card holds no personal data.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/first_sale" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The card, or
card: null. - 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Hide the first-sale card on the Overview#
POST /v2/projects/{project_id}/ai/first_sale/dismiss · Auth: dashboard session · RevenueDot extension
Hides it for everyone in the project, so admins and developers only.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/first_sale/dismiss"Responses
- 200: Hidden.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Attribution#
Where customers came from: each customer's attribution row (media source, campaign, ad group, keyword, ad, creative, Apple Search Ads ids, partner ids) and revenue by campaign with day-0, day-7, day-30 and to-date revenue. Charts take the same dimensions as filters and segments. See Attribution.
Get a customer's attribution#
GET /v2/projects/{project_id}/customers/{customer_id}/attribution · Auth: secret key or dashboard session · RevenueDot extension · Permissions: customer_information:customers:read
The first-class attribution row built from the customer's reserved attributes ($mediaSource, $campaign, $adGroup, $ad, $keyword, $creative, $appleAds*, $claimType, $conversionType) and partner ids. Null when the customer has none.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
customer_id |
string | yes | Any of the customer's app user ids. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/customers/user_1/attribution" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The row.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Revenue by campaign#
GET /v2/projects/{project_id}/attribution/report · Auth: secret key or dashboard session · RevenueDot extension · Permissions: charts_metrics:charts:read
New customers of a date range grouped by one attribution dimension, with trial starts, paying customers and revenue on day 0, by day 7, by day 30 and to date (USD, net of refunds in each window, ads excluded). See Attribution.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
group_by |
media_source, campaign, ad_group, keyword |
no | Default campaign. |
media_source |
string | no | Only customers with this media source; No attribution for customers without one. |
start_date |
string | no | First cohort day (UTC). Default: 29 days before end_date. |
end_date |
string | no | Last cohort day (UTC). Default: today. |
environment |
production, sandbox |
no | Default production. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/attribution/report" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: The report.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Benchmarks#
RevenueDot Cloud only: the project's metrics against anonymized percentiles of apps that share, by category, platform and country, and the opt-in setting. See Benchmarks.
The project's values against peers#
GET /v2/projects/{project_id}/benchmarks · Auth: secret key or dashboard session · RevenueDot extension · Permissions: charts_metrics:charts:read
RevenueDot Cloud only. The project's last 12 complete months against the percentiles of apps that share anonymized benchmarks, for one category, platform and country. A group is published only when 10 or more projects of 10 or more different owner accounts contribute; nothing about another project is returned. A project that does not share sees no peer numbers. See Benchmarks.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
category |
string | no | A category id or all; default the project's. |
platform |
all, ios, android |
no | |
country |
string | no | A two-letter country code; default all. |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/benchmarks" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Benchmarks.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Whether the project shares benchmarks#
GET /v2/projects/{project_id}/benchmarks/settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/benchmarks/settings" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Settings.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Share or stop sharing anonymized benchmarks#
POST /v2/projects/{project_id}/benchmarks/settings · Auth: secret key or dashboard session · RevenueDot extension · Permissions: project_configuration:projects:read_write
Admins only. Sharing needs the app's category. Turning sharing off deletes the project's values and rebuilds the groups without it at once. Audited as benchmarks_settings_updated. Answers 404 on a self-hosted server.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
share |
boolean | yes | |
category |
business, education, gaming, health_fitness, media_entertainment, photo_video, productivity, shopping, social_lifestyle, travel, utilities, other, null |
no |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/benchmarks/settings" -H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" -d '{"share":true,"category":"health_fitness"}'Responses
- 200: Saved.
- 400: The request is invalid. Returns V2Error.
- 401: No API key, or an unknown one. Returns V2Error.
- 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Growth insights#
3 to 5 weekly recommendations from RevenueDot AI with the numbers behind them, Refresh, and the weekly digest's one-click opt-out. See Growth insights.
This week's growth insights#
GET /v2/projects/{project_id}/ai/insights · Auth: secret key or dashboard session · RevenueDot extension · Permissions: charts_metrics:charts:read
The cached recommendations RevenueDot AI wrote for this week (or the last ready week), with the numbers they rest on. Reading never calls the model. See Growth insights.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/insights" -H "Authorization: Bearer $SECRET_KEY"Responses
- 200: Insights.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
Write this week's insights now#
POST /v2/projects/{project_id}/ai/insights/refresh · Auth: dashboard session · RevenueDot extension · Permissions: charts_metrics:charts:read
Admins and developers. RevenueDot AI reads the project's numbers with its read tools and writes 3 to 5 recommendations; the server keeps only those that cite the data pack. At most once an hour per project (429), and it counts against the person's RevenueDot AI allowance.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
project_id |
string | yes | Project id (proj...). |
Example request
curl -s -X POST "$REVENUEDOT_URL/v2/projects/$PROJECT_ID/ai/insights/refresh"Responses
- 200: The new insights.
- 401: No API key, or an unknown one. Returns V2Error.
- 402: Live data is paused: the project's owner went live more than 14 days ago without starting Pro (RevenueDot Cloud only). Sandbox reads (
environment=sandbox) and secret-key reads of one customer, subscription or purchase are never paused. Returns V2Error. - 403: The key lacks a permission, or a public key was used. Returns V2Error.
- 404: Not found in this project (another project's ids also answer 404). Returns V2Error.
- 409: It already exists, or it conflicts with another object. Returns V2Error.
- 429: Too many requests. Retry later. Returns V2Error.
- 503: The store could not be reached, or the feature is not configured on this server. Returns V2Error.
The digest's opt-out page#
GET /auth/insights/unsubscribe · Auth: none · RevenueDot extension
Shows a button; a GET changes nothing (mail scanners follow links).
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The signed token from the digest email. |
Example request
curl -s "$REVENUEDOT_URL/auth/insights/unsubscribe"Responses
- 200: The page.
- 404: The link is not valid.
Turn the weekly digest off (one click)#
POST /auth/insights/unsubscribe · Auth: none · RevenueDot extension
Turns the digest off for the person the token was issued to, also as RFC 8058 one-click unsubscribe from the List-Unsubscribe header.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes | The signed token from the digest email. |
Example request
curl -s -X POST "$REVENUEDOT_URL/auth/insights/unsubscribe"Responses
- 200: Done.
- 404: The link is not valid.
Share cards#
Public pages and images RevenueDot makes for sharing, such as the first-sale card. No API key; the token in the URL is the only key.
Public chart page#
GET /share/charts/{token} · Auth: none · RevenueDot extension
The chart snapshot as a page: the plot, summary values, the values table, Open Graph and Twitter tags. No scripts, noindex.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/share/charts/$TOKEN"Responses
- 200: The page.
- 304: Not modified (ETag).
- 404: Unknown link.
- 410: The link was revoked.
Public chart preview image#
GET /share/charts/{token}/og.png · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/share/charts/$TOKEN/og.png"Responses
- 200: A 1200×630 PNG.
- 404: Unknown link.
- 410: The link was revoked.
Public chart as SVG#
GET /share/charts/{token}/chart.svg · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/share/charts/$TOKEN/chart.svg"Responses
- 200: The 1200×630 card as SVG.
- 404: Unknown link.
- 410: The link was revoked.
Public first-sale page or image#
GET /share/first-sale/{token} · Auth: none · RevenueDot extension
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | yes |
Example request
curl -s "$REVENUEDOT_URL/share/first-sale/$TOKEN"Responses
- 200: An HTML page with Open Graph tags, or the SVG card.
- 404: Unknown card.
OAuth for MCP clients#
OAuth 2.1 with PKCE so MCP clients can connect to one project without copying a key.
OAuth authorization server metadata#
GET /.well-known/oauth-authorization-server · Auth: none · RevenueDot extension
RFC 8414 metadata for MCP clients (Claude, ChatGPT, Cursor ...). Endpoints are built from this server's public origin.
Example request
curl -s "$REVENUEDOT_URL/.well-known/oauth-authorization-server"Responses
- 200: Metadata.
Example 200 response:
{
"issuer": "https://revenuedot.example.com",
"authorization_endpoint": "https://revenuedot.example.com/oauth/authorize",
"token_endpoint": "https://revenuedot.example.com/oauth/token",
"registration_endpoint": "https://revenuedot.example.com/oauth/register",
"scopes_supported": [
"project:read",
"project:write"
],
"response_types_supported": [
"code"
],
"response_modes_supported": [
"query"
],
"grant_types_supported": [
"authorization_code"
],
"token_endpoint_auth_methods_supported": [
"none"
],
"code_challenge_methods_supported": [
"S256"
],
"service_documentation": "https://revenuedot.app/docs/mcp"
}Register an OAuth client#
POST /oauth/register · Auth: none · RevenueDot extension
Dynamic client registration (RFC 7591), public clients only. Redirect URIs must be https, http on localhost, or an app scheme such as cursor://.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
redirect_uris |
array of string | yes | |
client_name |
string | no | |
grant_types |
array of string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/oauth/register" \
-H "Content-Type: application/json" -d '{"client_name":"Claude","redirect_uris":["https://claude.ai/api/mcp/auth_callback"]}'Responses
- 201: The client.
- 400: Invalid metadata or redirect URI.
Consent screen#
GET /oauth/authorize · Auth: none · RevenueDot extension
An HTML page. The user signs in to the dashboard (the session cookie is reused), picks one project and read or read-write access. PKCE with S256 is required.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
response_type |
string | no | |
client_id |
string | no | |
redirect_uri |
string | no | |
state |
string | no | |
scope |
string | no | |
code_challenge |
string | no | |
code_challenge_method |
string | no | |
resource |
string | no |
Example request
curl -s "$REVENUEDOT_URL/oauth/authorize"Responses
- 200: The consent page.
- 302: Back to the client with an error.
- 400: Unknown client or redirect URI.
Submit the consent decision#
POST /oauth/authorize · Auth: dashboard session · RevenueDot extension
The consent form posts here. On allow, redirects to the client's redirect URI with a one-time code (valid 10 minutes).
Request body (application/x-www-form-urlencoded)
| Field | Type | Required | Description |
|---|---|---|---|
decision |
allow, deny |
no | |
project_id |
string | no | |
access |
project:read, project:write |
no | |
csrf |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/oauth/authorize"Responses
- 302: Redirect with
codeandstate, or witherror. - 403: The form expired.
Exchange a code for an access token#
POST /oauth/token · Auth: none · RevenueDot extension
authorization_code grant with PKCE. The access token is a secret API key (sk_...) bound to the chosen project with the approved permissions. It does not expire; revoke it on the project's API keys page.
Request body (application/x-www-form-urlencoded)
| Field | Type | Required | Description |
|---|---|---|---|
grant_type |
"authorization_code" |
yes | |
code |
string | yes | |
code_verifier |
string | yes | |
client_id |
string | no | |
redirect_uri |
string | no |
Example request
curl -s -X POST "$REVENUEDOT_URL/oauth/token"Responses
- 200: The token.
- 400: Invalid grant or request.
Objects#
The shapes the operations above send and return.
ActiveEntitlement#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"customer.active_entitlement" |
yes | |
entitlement_id |
string | yes | Entitlement id (entl...), not the lookup key. |
expires_at |
integer or null | yes | When access ends. Epoch milliseconds, or null. |
ApiKey#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"api_key" |
yes | |
id |
string | yes | |
name |
string | yes | |
prefix |
string | yes | First 7 characters of the key. |
permissions |
array of string | yes | Scopes; * is every scope. |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
last_used_at |
integer or null | yes | Last use, updated at most once a minute. Epoch milliseconds, or null. |
key |
string | no | The secret key (sk_...). Only in the answer that creates it. |
App#
Only the object for the app's own type is present. Store secrets are never returned.
| Field | Type | Required | Description |
|---|---|---|---|
object |
"app" |
yes | |
id |
string | yes | App id (app...). |
name |
string | yes | |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
type |
amazon, app_store, mac_app_store, play_store, stripe, rc_billing, roku, paddle, test_store, galaxy |
yes | |
project_id |
string | yes | |
custom_url_scheme |
string | no | Derived from the public key. |
app_store |
object | no | |
app_store.bundle_id |
string | no | |
app_store.app_store_connect_api_key_configured |
boolean | no | |
app_store.subscription_key_configured |
boolean | no | True when the in-app purchase key (.p8, key id, issuer id) is set. |
app_store.app_store_connect_vendor_number |
string or null | no | |
mac_app_store |
object | no | |
mac_app_store.bundle_id |
string | no | |
play_store |
object | no | |
play_store.package_name |
string | no | |
play_store.play_service_account_credentials_configured |
boolean | no | |
amazon |
object | no | |
amazon.package_name |
string | no | |
stripe |
object | no | |
stripe.stripe_account_id |
string or null | no | |
rc_billing |
object | no | |
rc_billing.stripe_account_id |
string or null | no | |
rc_billing.seller_company_name |
string | no | |
rc_billing.app_name |
string | no | |
rc_billing.support_email |
string or null | no | |
rc_billing.default_currency |
string | no | |
roku |
object | no | |
roku.roku_channel_id |
string or null | no | |
roku.roku_channel_name |
string or null | no | |
paddle |
object | no | |
paddle.paddle_is_sandbox |
boolean | no | True for a sandbox key (pdl_sdbx_apikey_…), or for an older key marked sandbox. |
paddle.paddle_api_key |
null | no | Always null: the key is never returned (RevenueCat's field). |
galaxy |
object | no | RevenueDot extension. |
galaxy.package_name |
string | no |
Collaborator#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"collaborator" |
yes | |
id |
string | yes | |
name |
string or null | no | |
email |
string | yes | |
role |
admin, developer, read_only |
yes | RevenueCat's role names. read_only is the dashboard's Viewer role. |
accepted_at |
integer | no | When the user joined. Epoch milliseconds. |
has_mfa |
boolean | no | Always false. |
CredentialsCheck#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"credentials_check" |
yes | |
app_id |
string | yes | |
store |
string | yes | |
status |
valid, invalid, unreachable |
yes | |
valid |
boolean | yes | |
message |
string | yes | What to do next, in plain words. |
checked_at |
integer | yes | Checked at. Epoch milliseconds. |
key_id |
string | no | |
client_email |
string or null | no | |
mode |
live, test |
no | Stripe: the key's mode. |
environment |
live, sandbox |
no | Paddle: the environment that accepted the key. |
service_account_id |
string | no | Galaxy Store: the service account checked. |
Customer#
active_entitlements and experiment are present on single-customer answers; attributes only with expand=attributes.
| Field | Type | Required | Description |
|---|---|---|---|
object |
"customer" |
yes | |
id |
string | yes | The customer's original app user id. |
project_id |
string | yes | |
first_seen_at |
integer | yes | First seen. Epoch milliseconds. |
last_seen_at |
integer or null | yes | Last seen. Epoch milliseconds, or null. |
last_seen_app_version |
string or null | no | |
last_seen_country |
string or null | no | |
last_seen_platform |
string or null | no | |
last_seen_platform_version |
null | no | |
active_entitlements |
object | no | |
active_entitlements.object |
"list" |
yes | |
active_entitlements.items |
array of ActiveEntitlement | yes | |
active_entitlements.next_page |
string or null | yes | Path of the next page, or null on the last page. |
active_entitlements.url |
string | yes | Path of this list. |
experiment |
ExperimentEnrollment or null | no | The experiment the customer is in (running or paused), else the last one they joined, or null. |
attributes |
object | no | |
attributes.object |
"list" |
yes | |
attributes.items |
array of CustomerAttribute | yes | |
attributes.next_page |
string or null | yes | Path of the next page, or null on the last page. |
attributes.url |
string | yes | Path of this list. |
CustomerAttribute#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"customer.attribute" |
yes | |
name |
string | yes | |
value |
string | yes | |
updated_at |
integer | yes | Last update. Epoch milliseconds. |
CustomerSummary#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"customer_summary" |
yes | |
id |
string | yes | The id you asked for. |
original_app_user_id |
string | yes | |
aliases |
array of string | no | |
total_revenue_in_usd |
number | no | |
sandbox_revenue_in_usd |
number | no | |
country |
string or null | no | |
platform |
string or null | no | |
stores |
array of string | no | |
offering_override |
object or null | no | The offering set for this customer only; it wins over targeting and experiments. |
offering_override.id |
string | no | |
offering_override.lookup_key |
string | no | |
offering_override.display_name |
string | no | |
current_offering |
object or null | no | The current offering the SDK returns for this customer now, resolved with the device details of their last SDK request (platform, app and SDK version, SDK flavor, OS version, storefront). Locale conditions never match here because the locale is not stored. Reading it enrolls nobody in an experiment; it shows the variant their next request would get. |
current_offering.id |
string | yes | |
current_offering.lookup_key |
string | yes | |
current_offering.display_name |
string | no | |
current_offering.source |
override, experiment, targeting, default |
yes | Why the customer gets it: their override, an experiment (one they are in, or a running one their next request would enroll them in), the first live targeting rule that matches, or the project's current offering. |
current_offering.rule_id |
string | no | With targeting. |
current_offering.rule_name |
string or null | no | With targeting. |
current_offering.experiment_id |
string | no | With experiment. |
current_offering.experiment_name |
string or null | no | With experiment. |
current_offering.variant |
a, b, c, d |
no | With experiment: the variant, a being the control. |
current_offering.variant_name |
string | no | With experiment: the variant's name, such as Control or Treatment B. |
blocked |
boolean | no | One of the customer's app user ids is blocked: no entitlements anywhere. |
active_entitlements |
array of object | no | |
granted_entitlements |
array of object | no | |
subscriptions |
array of object | no | |
purchases |
array of object | no |
Deleted#
| Field | Type | Required | Description |
|---|---|---|---|
object |
string | yes | The deleted object's type. |
id |
string | yes | |
deleted_at |
integer | yes | When it was deleted. Epoch milliseconds. |
Entitlement#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"entitlement" |
yes | |
id |
string | yes | Entitlement id (entl...). |
project_id |
string | yes | |
lookup_key |
string | yes | What apps check, for example pro. |
display_name |
string | yes | |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
state |
active, inactive |
yes | |
products |
object | no | |
products.object |
"list" |
yes | |
products.items |
array of Product | yes | |
products.next_page |
string or null | yes | Path of the next page, or null on the last page. |
products.url |
string | yes | Path of this list. |
Event#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"event" |
yes | |
id |
string | yes | |
type |
string | yes | |
environment |
production, sandbox |
yes | |
app_id |
string or null | no | |
customer_id |
string or null | no | Original app user id. |
app_user_id |
string or null | no | |
occurred_at |
integer | yes | When it happened. Epoch milliseconds. |
created_at |
integer | no | Recorded. Epoch milliseconds. |
body |
object | yes | The webhook event object, exactly as webhooks receive it. |
ExperimentEnrollment#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"experiment_enrollment" |
yes | |
id |
string | yes | Experiment id (prexp...). |
name |
string | yes | The experiment's name. |
variant |
a, b, c, d |
yes | The customer's variant: a is the control. |
Funnel#
| Field | Type | Required | Description |
|---|---|---|---|
object |
funnel |
yes | |
id |
string | yes | Funnel id (fnl_...). |
name |
string | yes | |
slug |
string | yes | The page's address in the project, unique among purchase links and funnels. |
app_id |
string or null | yes | The Stripe app it sells through. |
status |
draft, published |
yes | |
has_unpublished_changes |
boolean | yes | The draft differs from the published copy. |
url |
string | yes | The public page. |
published_at |
integer or null | yes | When it was last published. Epoch milliseconds, or null. |
created_at |
integer | yes | When it was created. Epoch milliseconds. |
updated_at |
integer | yes | When it last changed. Epoch milliseconds. |
steps |
integer | yes | Steps in the draft. |
draft |
FunnelDoc | no | A funnel: a theme and 1 to 30 steps. To publish, it needs exactly one paywall step and exactly one success step, the paywall before the success step, and the success step last. |
problems |
array of object | no | What blocks publishing. Empty: it can be published. Single reads and writes only. |
problems[].path |
string | yes | Such as steps[2].options[0].next. |
problems[].message |
string | yes | |
views_30d |
integer | no | Visitors in the last 30 days. Lists only. |
purchases_30d |
integer | no | Purchases in the last 30 days. Lists only. |
FunnelDoc#
A funnel: a theme and 1 to 30 steps. To publish, it needs exactly one paywall step and exactly one success step, the paywall before the success step, and the success step last.
| Field | Type | Required | Description |
|---|---|---|---|
theme |
FunnelTheme | yes | |
steps |
array of FunnelStep | yes |
FunnelStep#
One screen of a funnel. Every step has id (1-40 lower-case letters, digits, - or _, unique in the funnel), type, title (up to 120 characters) and an optional subtitle (up to 600).
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | |
type |
question |
yes | |
title |
string | yes | |
subtitle |
string or null | no | |
options |
array of object | yes | |
options[].id |
string | yes | Unique in the step. |
options[].label |
string | yes | Up to 80 characters. |
options[].next |
string or null | no | Go to this step id when chosen (a path). Unset: the next step. |
multiple |
boolean | no | Allow several answers. |
attribute |
string or null | no | Save the answer as this customer attribute (letters, digits, _ . - $). Several answers are joined with ", ". |
button_label |
string or null | no | |
body |
string or null | no | Up to 600 characters. |
image_url |
string or null | no | An https image. |
placeholder |
string or null | no | |
required |
boolean | no | Default true. |
offering |
string or null | no | Offering lookup key. Null: the current offering. |
features |
array of string | no | Bullet lines, up to 120 characters each. |
highlight_package |
string or null | no | Package lookup key selected first, such as $rc_annual. |
discount_id |
string or null | no | A web discount applied without a code. |
allow_codes |
boolean | no | Show the discount code field. |
show_redemption |
boolean | no | Show the redemption link and the store buttons. |
FunnelTheme#
| Field | Type | Required | Description |
|---|---|---|---|
background |
string | yes | Page background. |
text |
string | yes | Text colour. |
accent |
string | yes | Buttons, the progress bar and the selected option. |
button_text |
string | yes | Text on buttons. |
corner_radius |
integer | yes | 0 (square) to 24. |
ImportResult#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"import_result" |
yes | |
emit_events |
boolean | yes | |
customers |
array of object | yes | |
customers[].id |
string | no | |
customers[].status |
created, updated, merged |
no | |
customers[].subscriptions |
integer | no | |
customers[].purchases |
integer | no | |
customers[].needs_token_refresh |
integer | no | |
customers[].notes |
array of string | no |
ImportStatus#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"import_status" |
yes | |
customers |
integer | yes | |
subscriptions |
integer | yes | |
needs_token_refresh |
integer | yes | Google Play subscriptions still waiting for their purchase token. |
needs_token_refresh_by_app |
object | yes |
IndicativePrice#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"indicative_price" |
yes | |
currency |
string | yes | ISO 4217 code. |
country |
string or null | yes | US for a United States store price, the region code for another Google Play region, else null (Test Store and Stripe prices, other App Store territories). |
amount_micros |
integer | yes | Price in micros: 9.99 is 9990000. |
Invite#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"invite" |
yes | |
id |
string | yes | inv_... |
email |
string | yes | The invited address, lowercased. |
role |
admin, developer, viewer |
yes | The role the person gets when they accept. |
status |
pending, expired, accepted, revoked |
yes | Lists only show pending and expired. An expired invite can be resent. |
invited_by |
string or null | no | User id of the admin who last sent it. |
created_at |
integer | no | When it was created. Epoch milliseconds. |
last_sent_at |
integer | no | When the last email went out. Epoch milliseconds. |
expires_at |
integer | yes | When the link stops working: 7 days after it was last sent. Epoch milliseconds. |
MassExtension#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"subscription_mass_extension" |
yes | |
id |
string | yes | Request id. |
app_id |
string | yes | |
product_id |
string | yes | |
environment |
production, sandbox |
yes | |
extend_by_days |
integer | no | |
extend_reason_code |
string | no | |
storefront_country_codes |
array of string | no | |
complete |
boolean | yes | |
completed_at |
integer or null | no | |
succeeded_count |
integer or null | no | |
failed_count |
integer or null | no | |
requested_at |
integer | no |
MetricHistory#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"metric_history" |
yes | |
id |
string | yes | |
currency |
"USD" |
no | |
days |
integer | yes | |
environment |
production, sandbox |
yes | |
resolution |
"day" |
no | |
value |
number | no | |
previous_value |
number or null | no | |
values |
array of object | yes | |
values[].date |
string | no | YYYY-MM-DD |
values[].value |
number | no | |
last_updated_at |
integer | no | Computed at. Epoch milliseconds. |
MonetaryAmount#
| Field | Type | Required | Description |
|---|---|---|---|
currency |
string | yes | ISO 4217 currency code. |
gross |
number | yes | Gross amount. |
commission |
number | yes | Estimated store commission. |
tax |
number | yes | Tax. Always 0 today. |
proceeds |
number | yes | Gross minus commission. |
Price#
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | yes | Price in the purchase currency. |
currency |
string | yes | ISO 4217 currency code. |
Product#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"product" |
yes | |
id |
string | yes | Product id (prod...). |
store_identifier |
string | yes | The store's product id. Google Play subscriptions use subscriptionId:basePlanId; an App Store monthly billing plan (iOS 26.4) uses productId:monthly. |
type |
subscription, one_time, consumable, non_consumable, non_renewing_subscription |
yes | |
state |
active, inactive |
yes | |
subscription |
object | no | |
subscription.duration |
string or null | no | ISO 8601 period such as P1M. |
subscription.grace_period_duration |
null | no | |
subscription.trial_duration |
null | no | |
one_time |
object | no | |
one_time.is_consumable |
boolean or null | no | |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
app_id |
string | yes | |
display_name |
string or null | yes | |
app |
App | no | Only the object for the app's own type is present. Store secrets are never returned. |
indicative_price |
IndicativePrice or null | no | With expand=indicative_price: the Test Store price; else the App Store or Google Play price in the United States from the last store price read (or the in-app purchase's base territory, or the first territory with a price); else the Stripe web product's price. Null when none is known. |
store_details |
StoreDetails or null | no | RevenueDot extension, with expand=store_details: the store's status and price from the last store price read. Null when the product was never read from App Store Connect or Google Play (other stores, or no read yet). |
ProductEdit#
| Field | Type | Required | Description |
|---|---|---|---|
object |
product_edit |
yes | |
id |
string | yes | Product file id (pedit...). |
app_id |
string | yes | |
store |
app_store, mac_app_store, play_store |
yes | |
status |
invalid, ready, committing, committed, partially_committed, failed |
yes | invalid: the file has errors and nothing can be committed. ready: checked, waiting for a commit. committing: a commit has rows left; call commit again. committed: every row succeeded. partially_committed: some rows failed. failed: every row failed. |
file_name |
string | yes | The uploaded file's name. |
created_at |
integer | yes | When the file was uploaded. Epoch milliseconds. |
updated_at |
integer | yes | When the file or its options last changed. Epoch milliseconds. |
committed_at |
integer or null | yes | When the last commit finished. Epoch milliseconds, or null. |
created_by |
string or null | yes | Who uploaded it: a user id (usr_...) or an API key id. |
created_by_email |
string or null | no | The uploader's email, when a user uploaded it. Only in GET answers. |
errors |
array of object | yes | Every problem the check found (at most 500). Empty unless status is invalid. |
errors[].line |
integer or null | yes | The CSV line, or null for the whole file. |
errors[].message |
string | yes | |
warnings |
array of object | yes | Notes that do not stop a commit: ignored columns, ignored product fields, prices that change by more than 50%, rows without a price. |
warnings[].line |
integer or null | yes | |
warnings[].message |
string | yes | |
summary |
object | yes | Counts from the check. Empty when the file could not be read at all. |
summary.price_changes |
integer | no | Prices of existing products that change. |
summary.new_products |
integer | no | Products the commit creates. |
summary.new_product_prices |
integer | no | Prices of those new products. |
summary.unchanged |
integer | no | Rows whose price already matches the store. |
summary.products |
integer | no | Products with at least one change. |
summary.rows |
integer | no | Data rows in the file. |
options |
object | yes | Empty for Google Play files. |
options.preserve_current_price |
boolean | no | App Store only: Apple's preserveCurrentPrice for subscription price changes. True (the default) keeps existing subscribers on their current price. |
results |
object | no | How many changes are pending, succeeded and failed. |
results.pending |
integer | yes | |
results.succeeded |
integer | yes | |
results.failed |
integer | yes | |
rows |
array of ProductEditRow | no | Every change, in file order. Not in list answers. |
ProductEditRow#
| Field | Type | Required | Description |
|---|---|---|---|
object |
product_edit_row |
yes | |
idx |
integer | yes | Position of the change in the file's list of changes. |
kind |
price_change, new_product |
yes | price_change: a new price for an existing product, in a territory it has a price in or a new territory. new_product: a price of a product the commit creates. |
line |
integer or null | yes | The CSV line the change comes from. |
store_identifier |
string | yes | |
territory |
string | yes | App Store territory (USA) or Google Play region (US). |
currency |
string | yes | ISO 4217 code. |
old_amount_micros |
integer or null | yes | The store's price before the change, or null for a new territory or a new product. |
new_amount_micros |
integer | yes | The price in the file. |
change_percent |
number or null | yes | Change from the old price in percent, one decimal (10.0 for 9.99 to 10.99). Null without an old price. |
product |
object or null | yes | For new_product rows: the product the commit creates, from the file's type, duration, display_name and group columns. |
status |
pending, succeeded, failed |
yes | pending until the commit reaches the row. |
error |
string or null | yes | The store's message when the row failed. On a row that succeeded without a write, a note such as "Already at this price." |
attempts |
integer | yes | How many times the row was sent to the store. |
updated_at |
integer or null | yes | When the row last changed. Epoch milliseconds, or null. |
Project#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"project" |
yes | |
id |
string | yes | Project id (proj...). |
name |
string | yes | |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
icon_url |
string or null | no | Always null. |
icon_url_large |
string or null | no | Always null. |
ProjectSettings#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"project" |
yes | |
id |
string | yes | Project id (proj...). |
name |
string | yes | |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
icon_url |
string or null | no | Always null. |
icon_url_large |
string or null | no | Always null. |
transfer_behavior |
transfer, transfer_if_no_active, keep, share |
yes | What happens when a purchase already owned by another customer is restored. Default transfer. |
sandbox_transfer_behavior |
transfer, transfer_if_no_active, keep, share, null |
yes | Override for sandbox purchases; null uses transfer_behavior. |
sandbox_testing_access |
anybody, allowlist, nobody |
no | Who unlocks entitlements and in-app currency with sandbox purchases (Test Store purchases included). allowlist: only customers with an app user id in sandbox_testers. Others' sandbox purchases are recorded and sent to webhooks but unlock nothing. Default anybody. |
sandbox_testers |
array of string | no | App user ids allowed to test when sandbox_testing_access is allowlist. Up to 500. |
owner |
object or null | no | The project's owner, the only one who can transfer ownership. Null when no owner is recorded. |
owner.id |
string | no | User id. |
owner.email |
string | no | |
owner.name |
string or null | no |
PublicApiKey#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"public_api_key" |
yes | |
id |
string | yes | |
key |
string | yes | The key the SDK sends (appl_, goog_, test_ ...). |
environment |
production, sandbox |
yes | |
app_id |
string | yes | |
created_at |
integer | yes | Creation time. Epoch milliseconds. |
Purchase#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"purchase" |
yes | |
id |
string | yes | |
customer_id |
string | yes | |
original_customer_id |
string | no | |
product_id |
string | yes | |
purchased_at |
integer | yes | Purchase time. Epoch milliseconds. |
revenue_in_usd |
MonetaryAmount | no | |
quantity |
integer | no | |
status |
owned, refunded |
yes | |
presented_offering_id |
string or null | no | Offering the purchase was made from (its id, or the identifier the SDK sent when no such offering exists). |
entitlements |
object | no | |
entitlements.object |
"list" |
yes | |
entitlements.items |
array of Entitlement | yes | |
entitlements.next_page |
string or null | yes | Path of the next page, or null on the last page. |
entitlements.url |
string | yes | Path of this list. |
environment |
production, sandbox |
yes | |
store |
string | yes | |
store_purchase_identifier |
string | no | |
ownership |
purchased |
no | |
country |
string | no |
PurchaseLink#
| Field | Type | Required | Description |
|---|---|---|---|
object |
purchase_link |
yes | |
id |
string | yes | Purchase link id (plink_...). |
name |
string | yes | |
slug |
string | yes | The page's address in the project. |
app_id |
string | yes | The Stripe app it sells through. |
offering_id |
string | yes | |
offering_lookup_key |
string or null | no | |
offering_display_name |
string or null | no | |
discount_id |
string or null | no | A web discount applied without a code. |
expires_at |
integer or null | no | When the link stops working. Epoch milliseconds, or null. |
disabled_at |
integer or null | no | When it was turned off. Epoch milliseconds, or null. |
status |
active, expired, disabled |
yes | |
url |
string | yes | The public page. |
checkouts |
integer | yes | Checkouts started. |
purchases |
integer | yes | Checkouts paid. |
created_at |
integer | yes | When it was created. Epoch milliseconds. |
SetupHealth#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"setup_health" |
yes | |
project_id |
string | yes | |
checked_at |
integer | yes | Computed at. Epoch milliseconds. |
apps |
array of object | yes | |
apps[].id |
string | no | |
apps[].name |
string | no | |
apps[].type |
string | no | |
apps[].notification_url |
string or null | no | Where the store must send notifications. |
apps[].last_notification_at |
integer or null | no | Last notification processed for a known purchase (or the store's test). Epoch milliseconds, or null. |
apps[].last_notification_received_at |
integer or null | no | Last notification received at all. Epoch milliseconds, or null. |
apps[].last_notification_error |
object or null | no | |
apps[].last_notification_error.at |
integer | no | |
apps[].last_notification_error.type |
string or null | no | |
apps[].last_notification_error.message |
string | no | |
apps[].notification_status |
ready, failing, received, waiting |
no | |
apps[].rejected_requests |
object | no | |
apps[].rejected_requests.last_24h |
integer | yes | Requests without a valid signature in the last 24 hours. They never change notification_status. |
apps[].rejected_requests.last |
object or null | yes | The newest one, or null when there was none in 24 hours. |
apps[].credentials_configured |
boolean | no | |
webhooks |
object | yes | |
webhooks.total |
integer | no | |
webhooks.attempted_24h |
integer | no | |
webhooks.delivered_24h |
integer | no | |
webhooks.failed_24h |
integer | no | |
webhooks.pending |
integer | no | |
webhooks.delivered_percent_24h |
number or null | no | |
webhooks.failing |
array of object | no | |
webhooks.failing[].id |
string | no | |
webhooks.failing[].name |
string | no | |
webhooks.failing[].url |
string | no | |
webhooks.failing[].last_status |
integer or null | no | |
webhooks.failing[].last_error |
string or null | no | |
webhooks.failing[].last_attempt_at |
integer | no | |
webhooks.failing[].delivery_status |
string | no | |
sdk_versions |
array of object | yes |
StoreDetails#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"store_details" |
yes | |
status |
string or null | yes | The store's state in lower case: approved, ready_to_submit, waiting_for_review, in_review, rejected, developer_action_needed, missing_metadata, removed_from_sale (App Store), active, draft, inactive (Google Play). |
store_state |
string or null | yes | The state as the store spells it, such as APPROVED or ACTIVE. |
price |
object or null | yes | The United States price when the product has one, else the in-app purchase's base territory or the first territory with a price. Null when the store has no price for it. |
territories |
integer | yes | How many territories have a price. |
duration |
string or null | yes | The store's period for a subscription (P1M ...). |
display_name |
string or null | yes | The name in the store. |
editable |
boolean | yes | Whether the product editor can change its prices. |
refreshed_at |
integer | yes | When the prices were read from the store. Epoch milliseconds. |
refresh_status |
ok, failing, null |
yes | The app's last read: failing means it failed and these values are from the read before. |
StoreListing#
| Field | Type | Required | Description |
|---|---|---|---|
object |
store_listing |
yes | |
app_id |
string | yes | |
store_identifier |
string | yes | The identifier the catalog uses: the App Store product id, or subscription_id:base_plan_id (one-time products: the product id) on Google Play. |
product_id |
string or null | yes | The catalog product with this store identifier on this app, or null when the catalog does not have it. |
type |
subscription, one_time, consumable, non_consumable, non_renewing_subscription |
yes | |
display_name |
string or null | yes | App Store Connect's reference name, or the Play listing title. |
duration |
string or null | yes | ISO 8601 period of a subscription (P1M, P1Y ...). |
store_state |
string or null | yes | The store's own state: App Store review states such as APPROVED, READY_TO_SUBMIT, WAITING_FOR_REVIEW, MISSING_METADATA; Google Play base plan states ACTIVE, DRAFT, INACTIVE. |
status |
string or null | yes | store_state in lower case, such as approved or active. |
group |
object or null | yes | The App Store subscription group, or the Google Play subscription. |
store_id |
string or null | yes | The product's id in the store: the App Store Connect subscription or in-app purchase id, or the Google Play subscription or product id. |
price |
object or null | yes | The price shown for the product: the United States when it has a price there, else the in-app purchase's base territory, else the first territory with a price. Null when the store has no price for it. |
prices |
array of object | yes | The current price in every territory that has one. App Store prices scheduled for a later date are left out; an in-app purchase lists its manual prices and the prices Apple sets from the base territory. |
prices[].amount_micros |
integer | yes | Price in millionths of the currency unit: 9.99 is 9990000. |
prices[].currency |
string | yes | ISO 4217 code. |
prices[].territory |
string | yes | App Store Connect's three-letter territory code (USA) or Google Play's two-letter region code (US). |
editable |
boolean | yes | Whether the product editor can change its prices: true for App Store subscriptions and in-app purchases and Google Play base plans; false for Google Play one-time products and subscriptions without a base plan. |
note |
string or null | yes | Why the product cannot be edited, or a caveat from the store. |
refreshed_at |
integer | yes | When the prices were read from the store. Epoch milliseconds. |
StorePriceRefresh#
| Field | Type | Required | Description |
|---|---|---|---|
object |
store_price_refresh |
yes | |
app_id |
string | yes | |
warnings |
array of string | yes | Set when the store had more products than RevenueDot reads at once (5,000). |
sync |
StorePriceSync | yes | |
items |
array of StoreListing | yes | Every product the store has for the app, with its prices. |
StorePriceSync#
| Field | Type | Required | Description |
|---|---|---|---|
object |
store_price_sync |
yes | |
app_id |
string | yes | |
store |
app_store, mac_app_store, play_store |
yes | |
can_read_prices |
boolean | yes | Whether the app has the store key reading prices needs. |
reason |
string or null | yes | Why prices cannot be read, such as a missing App Store Connect API key. Null when they can. |
status |
never, ok, failing |
yes | never: not read yet. ok: the last read worked. failing: the last read failed and the prices from the read before stay. |
error |
string or null | yes | The last read's error. |
item_count |
integer | yes | Products in the last successful read. |
refreshed_at |
integer or null | yes | When the app's prices were last read, or tried. Epoch milliseconds, or null. |
StoreSettings#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"app_store_settings" |
yes | |
app_id |
string | yes | |
type |
string | yes | |
api_origin |
string | yes | This server as the outside world reaches it: the SDK's proxy URL. |
notification_url |
string or null | no | The store notification URL for this app (App Store, Google Play, Amazon, Stripe, Paddle, Roku or the Galaxy Store). |
notification_forward_url |
string or null | no | Where notifications are copied during a dual run. |
last_notification_at |
integer or null | no | Last notification processed for a known purchase. Epoch milliseconds, or null. |
last_notification_error |
string or null | no | |
last_notification_received_at |
integer or null | no | Last notification received. Epoch milliseconds, or null. |
notification_status |
ready, failing, received, waiting |
yes | |
rejected_requests |
object | no | |
rejected_requests.last_24h |
integer | yes | Requests without a valid signature in the last 24 hours. They never change notification_status. |
rejected_requests.last |
object or null | yes | The newest one, or null when there was none in 24 hours. |
rejected_requests.last.at |
integer | no | |
rejected_requests.last.message |
string | no | |
last_forward |
object or null | no | |
last_forward.status |
integer | no | HTTP status of the forward; 0 means no answer. |
last_forward.at |
integer | no | |
track_new_purchases |
boolean | no | Apply notifications about purchases this server has never seen. |
allow_unsigned_receipts |
boolean | no | Accept StoreKit 1 receipts without the in-app purchase key. Development only. |
credentials |
object | yes | |
credentials.subscription_key |
object | no | |
credentials.subscription_key.configured |
boolean | no | |
credentials.subscription_key.key_id |
string or null | no | |
credentials.subscription_key.issuer_id |
string or null | no | |
credentials.app_store_connect_api_key |
object | no | |
credentials.app_store_connect_api_key.configured |
boolean | no | |
credentials.app_store_connect_api_key.key_id |
string or null | no | |
credentials.app_store_connect_api_key.issuer_id |
string or null | no | |
credentials.app_store_connect_api_key.vendor_number |
string or null | no | |
credentials.shared_secret |
object | no | |
credentials.shared_secret.configured |
boolean | no | |
credentials.play_service_account |
object | no | |
credentials.play_service_account.configured |
boolean | no | |
credentials.play_service_account.client_email |
string or null | no | |
credentials.xcode_certificate |
object | no | |
credentials.xcode_certificate.configured |
boolean | no | |
credentials.amazon_shared_secret |
object | no | |
credentials.amazon_shared_secret.configured |
boolean | no | |
credentials.stripe_secret_key |
object | no | |
credentials.stripe_secret_key.configured |
boolean | no | |
credentials.stripe_secret_key.mode |
live, test, null |
no | |
credentials.stripe_secret_key.kind |
restricted, secret, other, null |
no | |
credentials.stripe_secret_key.last4 |
string or null | no | Last four characters of the key; the key itself is never returned. |
credentials.stripe_webhook_secret |
object | no | |
credentials.stripe_webhook_secret.configured |
boolean | no | |
credentials.paddle_api_key |
object | no | |
credentials.paddle_api_key.configured |
boolean | no | |
credentials.paddle_api_key.environment |
live, sandbox, null |
no | |
credentials.paddle_api_key.last4 |
string or null | no | |
credentials.paddle_webhook_secret |
object | no | |
credentials.paddle_webhook_secret.configured |
boolean | no | |
credentials.roku_api_key |
object | no | |
credentials.roku_api_key.configured |
boolean | no | |
credentials.galaxy_service_account |
object | no | |
credentials.galaxy_service_account.configured |
boolean | no | |
credentials.galaxy_service_account.service_account_id |
string or null | no | |
credentials.galaxy_iap_public_key |
object | no | |
credentials.galaxy_iap_public_key.configured |
boolean | no | |
sns_topic_arn |
string or null | no | Amazon: the only SNS topic notifications are accepted from, when set. |
stripe |
object or null | no | Stripe apps only. |
stripe.stripe_account_id |
string or null | no | |
stripe.app_user_id_source |
metadata, customer_id, anonymous |
no | |
stripe.app_user_id_metadata_key |
string | no | |
stripe.register_on |
invoice_paid, invoice_created |
no | |
stripe.configured |
boolean | no | |
paddle |
object or null | no | Paddle apps only. |
paddle.environment |
live, sandbox |
no | |
paddle.paddle_is_sandbox |
boolean | no | |
paddle.app_user_id_source |
custom_data, anonymous |
no | |
paddle.app_user_id_custom_data_key |
string | no | |
paddle.notification_setting_id |
string or null | no | The destination Apply in Paddle created. |
paddle.events |
array of string | no | The events the destination should send. |
paddle.configured |
boolean | no | |
roku |
object or null | no | Roku apps only. |
roku.roku_channel_id |
string or null | no | |
roku.roku_channel_name |
string or null | no | |
roku.configured |
boolean | no | |
galaxy |
object or null | no | Galaxy Store apps only. |
galaxy.package_name |
string or null | no | |
galaxy.service_account_id |
string or null | no | |
galaxy.configured |
boolean | no | |
galaxy.iap_public_key_configured |
boolean | no | |
small_business_program |
object or null | no | App Store, Mac App Store and Amazon apps: Apple's Small Business Program or Amazon's Small Business Accelerator Program, and the dates saved on the project's other apps of the store ("Use existing dates"). |
small_business_program.program |
app_store_small_business_program, amazon_small_business_accelerator |
no | |
small_business_program.rate |
number | no | Commission inside the periods: 0.15 (Apple) or 0.2 (Amazon). |
small_business_program.standard_rate |
number | no | Commission outside them: 0.3. |
small_business_program.enrolled |
boolean | no | |
small_business_program.periods |
array of object | no | |
small_business_program.periods[].entry_date |
string | no | YYYY-MM-DD |
small_business_program.periods[].exit_date |
string or null | no | YYYY-MM-DD, exclusive; null while still enrolled. |
small_business_program.other_apps |
array of object | no | |
small_business_program.other_apps[].app_id |
string | no | |
small_business_program.other_apps[].name |
string | no | |
small_business_program.other_apps[].enrolled |
boolean | no | |
small_business_program.other_apps[].periods |
array of object | no | |
sample_apps |
array of object | no | Samples GET …/sample_app can build for this app. |
sample_apps[].platform |
ios, android, flutter, react_native, web |
no | |
sample_apps[].name |
string | no | |
sample_apps[].example |
string | no | The folder in revenuedot/examples. |
Subscription#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"subscription" |
yes | |
id |
string | yes | Subscription id (sub_...). |
customer_id |
string | yes | |
original_customer_id |
string | no | |
product_id |
string or null | no | Product id (prod...), null for promotional grants. |
starts_at |
integer | yes | Start of the subscription. Epoch milliseconds. |
current_period_starts_at |
integer | no | Start of the current period. Epoch milliseconds. |
current_period_ends_at |
integer or null | no | End of the current period. Epoch milliseconds, or null. |
ends_at |
integer or null | no | End of access. Epoch milliseconds, or null. |
gives_access |
boolean | yes | |
pending_payment |
boolean | no | |
auto_renewal_status |
will_renew, will_not_renew, will_change_product, will_pause |
yes | |
status |
trialing, active, in_grace_period, in_billing_retry, paused, expired |
yes | |
total_revenue_in_usd |
MonetaryAmount | no | |
presented_offering_id |
string or null | no | Offering the purchase was made from (its id, or the identifier the SDK sent when no such offering exists). |
entitlements |
object | no | |
entitlements.object |
"list" |
yes | |
entitlements.items |
array of Entitlement | yes | |
entitlements.next_page |
string or null | yes | Path of the next page, or null on the last page. |
entitlements.url |
string | yes | Path of this list. |
environment |
production, sandbox |
yes | |
store |
string | yes | |
store_subscription_identifier |
string | no | Latest store transaction id, order id or token. |
ownership |
purchased, family_shared |
no | |
country |
string | no | ISO 3166-1 alpha-2, when known. |
management_url |
null | no |
TestPurchase#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"test_purchase" |
yes | |
scenario |
string | yes | |
store_transaction_id |
string | yes | The Test Store token (test_ |
event_types |
array of string | yes | Events recorded, in order. |
customer |
Customer | yes | active_entitlements and experiment are present on single-customer answers; attributes only with expand=attributes. |
subscription |
Subscription or null | no | |
purchase |
Purchase or null | no |
Transaction#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"transaction" |
yes | |
id |
string | yes | |
customer_id |
string | yes | |
app_id |
string or null | no | |
store |
string | yes | |
store_transaction_id |
string or null | no | |
product_identifier |
string | yes | |
kind |
purchase, renewal, trial, one_time, refund, refund_reversal |
yes | |
environment |
production, sandbox |
yes | |
purchased_at |
integer | yes | When the money moved. Epoch milliseconds. |
expires_at |
integer or null | no | End of the period. Epoch milliseconds, or null. |
revenue_in_usd |
number | yes | USD; negative for refunds. |
price |
Price or null | no | |
country |
string or null | no | |
project_id |
string | no | The transaction's project. Only on GET /v2/overview/transactions. |
V2Error#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"error" |
yes | |
type |
parameter_error, resource_already_exists, resource_missing, idempotency_error, rate_limit_error, authentication_error, authorization_error, store_error, server_error, resource_locked_error, unprocessable_entity_error, invalid_request, entity_references_archived_entities, plan_required |
yes | |
message |
string | yes | What went wrong. |
param |
string | no | The request field at fault, when there is one. |
doc_url |
string | yes | Link to the error's section of the errors page. |
retryable |
boolean | yes | True when retrying the same request can succeed. |
upgrade_url |
string | no | Only on plan_required: the Billing page where the owner starts Pro. |
WebConfig#
| Field | Type | Required | Description |
|---|---|---|---|
object |
web_config |
yes | |
app_id |
string | yes | The Stripe app. |
saved |
boolean | yes | False until the first save; the values are then the defaults. |
updated_at |
integer or null | no | Last save. Epoch milliseconds, or null. |
app_name |
string | yes | Shown at the top of every page. Default: the app's name. |
logo_url |
string or null | no | Logo. An https URL. |
theme |
FunnelTheme | yes | |
terms_url |
string or null | no | Terms of service. An https URL. |
privacy_url |
string or null | no | Privacy policy. An https URL. |
support_email |
string or null | no | Reply-to address of the redemption email and a Support link on pages. |
success_mode |
show_redemption, redirect |
yes | After payment: RevenueDot's success page, or a 303 redirect to success_redirect_url with redemption_url in the query. |
success_redirect_url |
string or null | no | Required for redirect. An https URL. |
success_title |
string or null | no | Success page title. Default: "Thank you for your purchase". |
success_body |
string or null | no | A line under the title. |
cancel_url |
string or null | no | Where a cancelled checkout returns. Default: the page, with ?canceled=1. An https URL. |
app_scheme |
string | yes | The app's URL scheme for redemption links, such as myapp. Default: rd- and 10 hex characters of the project id's hash. |
app_store_url |
string or null | no | App Store button on the success and redemption pages. An https URL. |
play_store_url |
string or null | no | Google Play button. An https URL. |
redemption_link_hours |
integer | yes | How long a redemption link works, 1 to 720 hours. Default 24. |
presets |
array of object | no | Colour presets: Ink, Night, Ocean, Forest, Sunset. |
presets[].name |
string | yes | |
presets[].theme |
FunnelTheme | yes |
WebDiscount#
| Field | Type | Required | Description |
|---|---|---|---|
object |
web_discount |
yes | |
id |
string | yes | Discount id (disc...). |
identifier |
string | yes | Your identifier for the discount, unique in the project. |
customer_facing_name |
string | yes | The name buyers see at checkout and Stripe puts on the coupon (first 40 characters). |
duration_mode |
one_time, time_window, forever |
yes | one_time: the first payment. time_window: every payment for time_window. forever: every payment. |
eligibility |
everyone, never_purchased, never_subscribed, never_subscribed_to_the_same_product |
yes | Who may use it, checked against the buyer's purchase history when the checkout has an app user id. |
time_window |
string or null | no | ISO 8601 months or years (P3M, P1Y), 1 to 36 months, for time_window. Otherwise null. |
disabled_at |
integer or null | no | When it was disabled. Epoch milliseconds, or null. |
type |
percentage, fixed_amount |
yes | |
percentage |
integer | no | For percentage. |
fixed_amount |
object | no | For fixed_amount. |
created_at |
integer | no | When it was created. Epoch milliseconds. |
updated_at |
integer | no | When it last changed. Epoch milliseconds. |
label |
string | yes | How checkout describes it, such as "20% off for 3 months". |
product_identifiers |
array of string | no | The products it applies to (store identifiers or product ids). Empty: every product. |
max_redemptions |
integer or null | yes | Paid checkouts it may be used in, or null for no cap. |
expires_at |
integer or null | yes | When it stops working. Epoch milliseconds, or null. |
times_redeemed |
integer | yes | Paid checkouts that used it. |
status |
active, disabled, expired, used_up |
yes | |
stripe |
array of object | yes | |
stripe[].app_id |
string | yes | The Stripe app. |
stripe[].coupon_id |
string | yes | The coupon in that Stripe account. |
codes |
array of object | yes | |
codes[].code |
string | yes | |
codes[].times_redeemed |
integer | yes | |
codes[].created_at |
integer | yes | When it was created. Epoch milliseconds. |
codes[].stripe_promotion_codes |
object or null | no | Stripe promotion code id by Stripe app id. |
WebDomain#
| Field | Type | Required | Description |
|---|---|---|---|
object |
web_domain |
yes | |
slug |
string | yes | The project's address on RevenueDot's pay host. |
pay_base |
string | yes | REVENUEDOT_PAY_URL, or <server>/pay. |
default_base |
string | yes | <pay_base>/<slug>. |
base |
string | yes | Where the project's pages live now: the custom domain once verified, else default_base. |
custom_domain |
string or null | yes | |
status |
none, pending, verified, failed |
yes | |
verified_at |
integer or null | no | When DNS was first proven. Epoch milliseconds, or null. |
checked_at |
integer or null | no | Last check. Epoch milliseconds, or null. |
error |
string or null | no | Why the last check failed. |
dns |
array of object | yes | The records to add at your DNS provider. |
dns[].type |
CNAME, TXT |
yes | |
dns[].name |
string | yes | |
dns[].value |
string | yes | |
cloud_note |
string or null | no | On RevenueDot Cloud: the extra step the RevenueDot team does (the TLS certificate). |
found |
object | no | Verify only: what DNS answered. |
found.cname |
array of string | no | |
found.txt |
array of string | no |
WebProduct#
| Field | Type | Required | Description |
|---|---|---|---|
object |
web_product |
yes | |
product |
Product | yes | |
stripe_product_id |
string | yes | prod_... in your Stripe account. |
stripe_price_id |
string | yes | price_...; also the product's store_identifier. |
price |
object | yes | |
price.amount |
number | yes | Major units. |
price.amount_minor |
integer | yes | Minor units, as Stripe stores it. |
price.currency |
string | yes | ISO 4217, upper case. |
interval |
day, week, month, year, null |
yes | Billing interval, or null for a one-time price. |
interval_count |
integer or null | yes | Intervals per period, such as 3 for every 3 months. |
trial_days |
integer or null | yes | Free trial days added at checkout. |
created_at |
integer | yes | When it was created. Epoch milliseconds. |
WebhookDelivery#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"webhook_delivery" |
yes | |
id |
string | yes | |
webhook_integration_id |
string | yes | |
event_id |
string | yes | |
event_type |
string | yes | |
status |
pending, delivered, failed, held |
yes | held: a production event of a project whose owner is paused on RevenueDot Cloud (no plan 14 days after the first live sale). It is sent, oldest first, when Pro starts; held more than 30 days, it is marked failed. |
attempts |
integer | yes | |
next_attempt_at |
integer or null | no | Next retry, when pending. Epoch milliseconds, or null. |
response_status |
integer or null | no | HTTP status of the last attempt. |
response_ms |
integer or null | no | Duration of the last attempt. |
last_error |
string or null | no | |
created_at |
integer | no | Queued at. Epoch milliseconds. |
WebhookState#
| Field | Type | Required | Description |
|---|---|---|---|
object |
"webhook_state" |
yes | |
id |
string | yes | Webhook id (wh_...). |
enabled |
boolean | yes | False while deliveries are paused. |