Which endpoints do the RevenueCat SDKs call on RevenueDot?

The RevenueCat SDKs call these endpoints when their proxy URL points at RevenueDot. They take a public app key (appl_, goog_, test_ ...), which is safe to ship in an app. Several also accept a secret key for server-side use. Errors use the SDK's error format { "code": 7103, "message": "..." }; see Errors. Responses under /v1 and /rcbilling are signed when the server has a signing key; see Trusted Entitlements.

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 (44)#

Server#

Health and server info. No API key.

GET / · Auth: none

Answers a small JSON document. The Docker health check calls it.

Example request

Shell
curl -s "$REVENUEDOT_URL/"

Responses

  • 200: Server info.

Example 200 response:

JSON
{
  "name": "RevenueDot",
  "docs": "https://revenuedot.app/docs"
}

Health check#

GET /v1/health · Auth: none

Needs no API key. Use it for load balancer and uptime checks.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/health"

Responses

  • 200: The server is up.

Example 200 response:

JSON
{
  "status": "ok"
}

Connectivity probe#

GET /v1/health/connectivity · Auth: none

Needs no API key. The iOS SDK probes it only with its internal API failover setting, which is off by default.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/health/connectivity"

Responses

  • 200: The server is up.

Example 200 response:

JSON
{
  "status": "ok"
}

Customer info#

The customer's entitlements, subscriptions and one-time purchases, as the SDK decodes them into CustomerInfo.

Get customer info#

GET /v1/subscribers/{app_user_id} · Auth: public app key or secret key

What Purchases.getCustomerInfo() calls. Creates the customer when the app user id is new (answer 201). With a secret key the answer also has subscriber_attributes. Entitlements are listed even after they expire; an entitlement is active while expires_date is null or in the future.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Headers

Name Type Required Description
X-Nonce string no Base64 nonce the SDK sends when entitlement verification is on; it is part of the signed message.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Customer info. Returns CustomerInfo.
  • 201: Customer info of a customer created by this call. Returns CustomerInfo.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}

Receipts#

Purchases, restores and syncs. RevenueDot verifies them with the store.

Post a purchase or restore#

POST /v1/receipts · Auth: public app key or secret key

Every purchase, restore and syncPurchases() ends here. RevenueDot verifies the purchase with the store, saves it, records events and answers the updated customer info.

  • App Store: fetch_token is a StoreKit 2 signed transaction (JWS), a StoreKit 1 app receipt (base64) or an Xcode StoreKit test receipt. With the app's in-app purchase key, Apple's App Store Server API supplies the full history and renewal state.
  • Google Play: fetch_token is the purchase token. RevenueDot checks it with the Play Developer API and acknowledges it.
  • Test Store: fetch_token is test_<purchase time in ms>_<id>. Any such token is accepted.

4xx or 5xx matters. A 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. RevenueDot answers 5xx for its own and the store's temporary failures so the SDK keeps the purchase and retries. With a secret key, send X-Platform so RevenueDot knows which app the receipt belongs to.

Headers

Name Type Required Description
X-Platform string no SDK platform (ios, android, macos, web ...). With a secret key it picks the project's app for that platform.
X-Nonce string no Base64 nonce the SDK sends when entitlement verification is on; it is part of the signed message.
X-Is-Sandbox string no true when the SDK knows the purchase is sandbox (used for StoreKit 1 receipts without an environment).

Request body (application/json)

Field Type Required Description
app_user_id string yes The customer posting the receipt.
fetch_token string no Receipt, signed transaction, purchase token or Test Store token.
app_transaction string no StoreKit 2 app transaction JWS (accepted; not required).
transaction_id string no Store transaction id.
product_id string no Product being bought.
product_ids array of string no
platform_product_ids array of object no
platform_product_ids[].product_id string no
platform_product_ids[].base_plan_id string no
platform_product_ids[].offer_id string no
price number no
currency string no
store_country string no
normal_duration string no ISO 8601 period of the product.
is_restore boolean no
store_user_id string no
presented_offering_identifier string no Offering the purchase was made from; it appears in webhooks.
attributes object no Customer attributes to save with the purchase.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/receipts" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1","fetch_token":"test_1790800914000_quickstart","product_id":"pro_monthly","price":9.99,"currency":"USD","presented_offering_identifier":"default"}'

Responses

  • 200: Updated customer info, plus purchased_products. Returns ReceiptResponse.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.
  • 500: Server error. The SDK keeps the purchase and retries. Returns V1Error.
  • 503: The store could not be reached. Retry later. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  },
  "purchased_products": {
    "pro_monthly": {
      "should_consume": false
    }
  }
}

Offerings (SDK)#

What the paywall shows.

Get offerings#

GET /v1/subscribers/{app_user_id}/offerings · Auth: public app key or secret key

What Purchases.getOfferings() calls. Lists active offerings with the packages whose product belongs to the calling app. current_offering_id is the customer's override when one is set.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/offerings" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

Example 200 response:

JSON
{
  "current_offering_id": "default",
  "offerings": [
    {
      "description": "Standard plans",
      "identifier": "default",
      "metadata": null,
      "packages": [
        {
          "identifier": "$rc_monthly",
          "platform_product_identifier": "pro_monthly"
        },
        {
          "identifier": "$rc_annual",
          "platform_product_identifier": "pro_annual"
        },
        {
          "identifier": "$rc_lifetime",
          "platform_product_identifier": "pro_lifetime"
        }
      ]
    }
  ]
}

Get offerings without a user#

GET /v1/offerings · Auth: public app key or secret key

Same answer as the per-user call, without a customer override.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/offerings" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

Test Store product details#

GET /rcbilling/v1/subscribers/{app_user_id}/products · Auth: public app key

Product details the SDK needs for Test Store (and web) products, in the web billing products shape. Prices are 0 until the catalog stores Test Store prices.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Query parameters

Name Type Required Description
id array of string no Product ids; repeat the parameter. None lists every product of the app.

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/subscribers/user_1/products" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Product details.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "product_details": [
    {
      "identifier": "pro_monthly",
      "product_type": "subscription",
      "title": "Pro monthly",
      "description": null,
      "current_price": {
        "amount": 0,
        "amount_micros": 0,
        "currency": "USD"
      },
      "normal_period_duration": "P1M",
      "default_purchase_option_id": "base",
      "default_subscription_option_id": "base",
      "purchase_options": {
        "base": {
          "id": "base",
          "price_id": "base",
          "base": {
            "period_duration": "P1M",
            "cycle_count": 1,
            "price": {
              "amount": 0,
              "amount_micros": 0,
              "currency": "USD"
            }
          },
          "base_price": null,
          "trial": null,
          "intro_price": null
        }
      },
      "subscription_options": {
        "base": {
          "id": "base",
          "price_id": "base",
          "base": {
            "period_duration": "P1M",
            "cycle_count": 1,
            "price": {
              "amount": 0,
              "amount_micros": 0,
              "currency": "USD"
            }
          },
          "base_price": null,
          "trial": null,
          "intro_price": null
        }
      }
    }
  ]
}

Identity#

logIn and aliases.

Log in (identify)#

POST /v1/subscribers/identify · Auth: public app key or secret key

What Purchases.logIn() calls. When new_app_user_id is new and the current id is anonymous with no other ids, the anonymous customer takes the new id (201). When new_app_user_id exists, an anonymous-only current customer is merged into it (200). See Customers and app user IDs.

Request body (application/json)

Field Type Required Description
app_user_id string yes The current app user id.
new_app_user_id string yes Your user id.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/identify" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"$RCAnonymousID:abc123","new_app_user_id":"user_2"}'

Responses

  • 200: The user existed. Returns CustomerInfo.
  • 201: The user is new. Returns CustomerInfo.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}

Alias two app user ids#

POST /v1/subscribers/{app_user_id}/alias · Auth: public app key or secret key

Links new_app_user_id to the customer with the same merge rules as log in. The Android SDK uses it for Block Store recovery.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
new_app_user_id string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/alias" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Accepted.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

Attributes#

Customer attributes such as $email.

Set customer attributes#

POST /v1/subscribers/{app_user_id}/attributes · Auth: public app key or secret key

Saves attributes such as $email, $displayName or your own keys. A null value deletes the attribute. An invalid $email is refused with 7263; the other attributes are saved. collectDeviceIdentifiers() sends $ip and $deviceVersion as "true": RevenueDot stores the request's IP address and the device and OS from the SDK's headers instead.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
attributes object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/attributes" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"attributes":{"$email":{"value":"ana@example.com","updated_at_ms":1790800914000}}}'

Responses

  • 200: Saved.
  • 400: Some attributes were not saved. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

SDK support#

Endpoints the SDK calls for features RevenueDot answers minimally, so the SDK keeps working.

Intro offer eligibility (StoreKit 1)#

POST /v1/subscribers/{app_user_id}/intro_eligibility · Auth: public app key or secret key

Answers null (unknown) for every product, so the SDK decides eligibility on the device.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
product_identifiers array of string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/intro_eligibility" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Eligibility per product.

Example 200 response:

JSON
{
  "pro_monthly": null
}

Sign a promotional offer (iOS)#

POST /v1/offers · Auth: public app key or secret key

What Purchases.promotionalOffer(forProductDiscount:product:) calls. RevenueDot signs each offer with the App Store app's In-App Purchase key (key_id, issuer_id and private_key in the app's credentials) the way Apple verifies it: ECDSA P-256 with SHA-256 over the bundle id, key id, product id, offer id, app account token, nonce and timestamp, DER-encoded and base64 (Apple's format). The app account token matches what the SDK puts on the payment: with StoreKit 2 the lowercase app user id when it is a UUID and empty otherwise; with StoreKit 1 the app user id. Without an In-App Purchase key the answer is 400 with code 7234, which the SDK reports as invalidAppleSubscriptionKeyError for that offer only.

Request body (application/json)

Field Type Required Description
app_user_id string yes
fetch_token string no The receipt or signed transaction; not needed for signing.
generate_offers array of object yes
generate_offers[].offer_id string yes Promotional offer id from App Store Connect.
generate_offers[].product_id string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/offers" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1","fetch_token":"…","generate_offers":[{"offer_id":"winback_50","product_id":"pro_monthly"}]}'

Responses

  • 200: One signature per offer.
  • 400: No In-App Purchase key, or no offers. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "offers": [
    {
      "key_id": "2X9R4HXF34",
      "offer_id": "winback_50",
      "product_id": "pro_monthly",
      "signature_data": {
        "nonce": "0f3c2a8e-5d7b-4d7e-9a53-3b8f2c1e6d40",
        "signature": "MEUCIQDD…",
        "timestamp": 1790800914034
      }
    }
  ]
}

Attribution data (deprecated iOS call)#

POST /v1/subscribers/{app_user_id}/attribution · Auth: public app key or secret key

What the deprecated Purchases.addAttributionData calls. The advertising identifiers in data (rc_idfa, rc_idfv, rc_gps_adid, rc_ip_address) become $idfa, $idfv, $gpsAdId and $ip. For Apple Search Ads (network 0) with iad-attribution true, the iAd fields become $mediaSource ("Apple Search Ads"), $campaign, $adGroup, $keyword, $creative and the $appleAds* ids. Attribution is write-once: a campaign attribute the customer already has is kept.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
network integer yes The SDK's AttributionNetwork: 0 Apple Search Ads.
data object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/attribution" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"network":0,"data":{"rc_idfv":"4CEE1BEE-3C19-4591-9E34-1AD968D7B609","Version3.1":{"iad-attribution":"true","iad-campaign-name":"Spring","iad-keyword":"scanner"}}}'

Responses

  • 200: Stored.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

Apple AdServices token#

POST /v1/subscribers/{app_user_id}/adservices_attribution · Auth: public app key or secret key

What enableAdServicesAttributionTokenCollection() sends once per install (the same token can also arrive as aad_attribution_token on a receipt). After answering, RevenueDot looks the token up with Apple's attribution API, retrying a 404 or 5xx 3 times 5 seconds apart, and stores an attributed install as $mediaSource ("Apple Search Ads"), $campaign, $adGroup, $keyword, $ad, $appleAdsCampaignId, $appleAdsAdGroupId, $appleAdsKeywordId, $appleAdsAdId, $appleAdsOrgId, $appleAdsCountryOrRegion, $claimType and $conversionType. Apple returns ids, not names. They show on the customer page and in every webhook's subscriber_attributes.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
aad_attribution_token string yes The token from AAAttribution.attributionToken().

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/adservices_attribution" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"aad_attribution_token":"wD3Ma…"}'

Responses

  • 200: Accepted; the lookup runs after the answer.
  • 400: No token. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

SDK health report availability#

GET /v1/subscribers/{app_user_id}/health_report_availability · Auth: none

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/health_report_availability"

Responses

  • 200: No report logs.

Example 200 response:

JSON
{
  "report_logs": false
}

SDK health report#

GET /v1/subscribers/{app_user_id}/health_report · Auth: public app key

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/health_report" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Always passed.

Example 200 response:

JSON
{
  "status": "passed",
  "project_id": "proj18pzzkao",
  "app_id": "appvnrm0a5h",
  "checks": []
}

Product to entitlement mapping (offline entitlements)#

GET /v1/product_entitlement_mapping · Auth: public app key or secret key

Lets the SDK grant entitlements while the server cannot be reached.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/product_entitlement_mapping" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: The mapping.

Example 200 response:

JSON
{
  "product_entitlement_mapping": {
    "pro_monthly": {
      "product_identifier": "pro_monthly",
      "entitlements": [
        "pro"
      ]
    }
  }
}

Customer Center configuration (not built)#

GET /v1/customercenter/{app_user_id} · Auth: public app key

Always 404 with code 7259: the SDK returns an error and the Customer Center screen shows its error state. Customer Center configuration is planned for Tier 2.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customercenter/user_1" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 404: Not configured. Returns V1Error.

Customer Center support ticket (not built)#

POST /v1/customercenter/support/create-ticket · Auth: public app key

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customercenter/support/create-ticket" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Not sent.

Example 200 response:

JSON
{
  "sent": false
}

Virtual currency balances (not built)#

GET /v1/subscribers/{app_user_id}/virtual_currencies · Auth: public app key

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/virtual_currencies" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Empty balances.

Example 200 response:

JSON
{
  "virtual_currencies": {}
}

Redeem a web purchase (not available)#

POST /v1/subscribers/redeem_purchase · Auth: public app key

What Purchases.redeemWebPurchase() calls with the redemption_token from a redemption deep link. RevenueDot takes no web payments, so no token is valid: 400 with code 7849, which the SDKs return as the invalidToken result.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/redeem_purchase" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Invalid token. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Register an Apple external purchase token (iOS)#

POST /v1/external_purchase_tokens · Auth: public app key

Part of Apple's external purchase and link-out flows, before a web checkout. The token is acknowledged with an id, which is all the SDK reads; the web checkout that follows is not available (see /rcbilling/v1/hosted-checkout).

Request body (application/json)

Field Type Required Description
app_user_id string yes
purchase_type IN_APP, LINK_OUT yes
token string no Apple's external purchase token, when there is one.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/external_purchase_tokens" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Registered.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "id": "ept3b1f0c9e2d8a4f6b9c7e5d3a1b2c4d6e",
  "purchase_type": "LINK_OUT",
  "is_sandbox": true,
  "token_source": "APPLE_SDK"
}

Rewarded ad verification (not available)#

GET /v1/subscribers/{app_user_id}/ads/reward_verifications/{client_transaction_id} · Auth: public app key

What pollRewardVerification polls. There is no server-side ad verification, so the answer is always the final failed, and the SDK stops after one request.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).
client_transaction_id string yes From generateRewardVerificationToken.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/ads/reward_verifications/$CLIENT_TRANSACTION_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Failed.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "status": "failed",
  "reward": null,
  "failure_reason": "not_supported",
  "message": "Server-side reward verification is not available on RevenueDot."
}

Amazon receipt details (not supported)#

GET /v1/receipts/amazon/{store_user_id}/{receipt_id} · Auth: public app key

The Android SDK asks for it on Amazon subscription purchases. Amazon Appstore purchases are not supported: 400 with code 7662, the same answer as a receipt post for an Amazon app, which leaves the purchase unconsumed.

Path parameters

Name Type Required Description
store_user_id string yes
receipt_id string yes Not encoded by the SDK; may contain /.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/receipts/amazon/$STORE_USER_ID/$RECEIPT_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not supported. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Paywall workflows (web SDK)#

GET /v1/subscribers/{app_user_id}/workflows · Auth: public app key

What purchases-js presentPaywall asks first. There are no workflows, so the SDK uses the offering's own paywall.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Query parameters

Name Type Required Description
type string no paywall.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/workflows" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: No workflows.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "workflows": [],
  "ui_config": {}
}

One paywall workflow (web SDK)#

GET /v1/subscribers/{app_user_id}/workflows/{workflow_id} · Auth: public app key

Never called, because the workflow list is empty.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).
workflow_id string yes

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/workflows/$WORKFLOW_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 401: Unknown API key. Returns V1Error.
  • 404: No such workflow. Returns V1Error.

Restore eligibility (StoreKit 2)#

POST /v1/subscribers/{app_user_id}/restore/eligibility · Auth: public app key

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/restore/eligibility" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Always allowed.

Example 200 response:

JSON
{
  "is_purchase_allowed_by_restore_behavior": true
}

Remote config (none yet)#

GET /v1/config/{domain} · Auth: public app key

Answers 204 (no config). getOfferings waits on this call.

Path parameters

Name Type Required Description
domain string yes Config domain the SDK asks for (for example app).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/config/app" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 204: No config.

Remote config (none yet)#

POST /v1/config/{domain} · Auth: public app key

Path parameters

Name Type Required Description
domain string yes Config domain the SDK asks for (for example app).

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/config/app" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 204: No config.

SDK paywall and feature events (accepted, not stored)#

POST /v1/events · Auth: public app key

Accepted so the SDK does not resend them forever.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/events" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Accepted.

Example 200 response:

JSON
{}

SDK diagnostics (accepted, not stored)#

POST /v1/diagnostics · Auth: public app key

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/diagnostics" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Accepted.

Example 200 response:

JSON
{}

Web Billing#

Web checkout calls from the iOS SDK and purchases-js. RevenueDot takes no web payments, so a checkout answers an error the SDK shows as a failed purchase.

Web offering products#

GET /rcbilling/v1/subscribers/{app_user_id}/offering_products · Auth: public app key

Defined in the iOS SDK with no caller. There are no web offerings.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/subscribers/user_1/offering_products" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: No web offerings.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "offerings": {}
}

Start a hosted web checkout (not available)#

POST /rcbilling/v1/hosted-checkout · Auth: public app key

The iOS SDK's paywall web checkout. RevenueDot takes no payments: 400 with code 7000, and the SDK returns failed for the checkout without retrying.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/hosted-checkout" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Web Billing purchase (not available)#

POST /rcbilling/v1/purchase · Auth: public app key

Defined in purchases-js with no caller. 400 with code 7000.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/purchase" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Prepare a Web Billing checkout (not available)#

POST /rcbilling/v1/checkout/prepare · Auth: public app key

purchases-js with an rcb_ key. 400 with code 7000: the purchase fails with an error in the SDK's purchase screen.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/checkout/prepare" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Start a Web Billing checkout (not available)#

POST /rcbilling/v1/checkout/start · Auth: public app key

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/checkout/start" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Web Billing checkout status#

GET /rcbilling/v1/checkout/{operation_session_id} · Auth: public app key

No checkout session exists: 400 with code 7877.

Path parameters

Name Type Required Description
operation_session_id string yes

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/checkout/$OPERATION_SESSION_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: No such session. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Refresh Web Billing checkout pricing#

PATCH /rcbilling/v1/checkout/{operation_session_id} · Auth: public app key

Path parameters

Name Type Required Description
operation_session_id string yes

Example request

Shell
curl -s -X PATCH "$REVENUEDOT_URL/rcbilling/v1/checkout/$OPERATION_SESSION_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: No such session. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Complete a Web Billing checkout#

POST /rcbilling/v1/checkout/{operation_session_id}/complete · Auth: public app key

Path parameters

Name Type Required Description
operation_session_id string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/checkout/$OPERATION_SESSION_ID/complete" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: No such session. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Web checkout branding#

GET /rcbilling/v1/branding · Auth: public app key

purchases-js with an rcb_ key loads it before a checkout: the app's name and the SDK's default look.

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/branding" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Branding.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "id": "appvnrm0a5h",
  "app_name": "Scanner Web",
  "app_icon": null,
  "app_icon_webp": null,
  "app_wordmark": null,
  "app_wordmark_webp": null,
  "appearance": null,
  "support_email": null,
  "gateway_tax_collection_enabled": false,
  "brand_font_config": null
}

Store notifications#

Where App Store Connect and Google Pub/Sub send server notifications.

App Store Server Notifications v2#

POST /v1/notifications/apple/{app_id} · Auth: none

Set this URL (shown on the app's page in the dashboard) as the Production and Sandbox Server URL in App Store Connect, with version 2 notifications. RevenueDot verifies Apple's signature and the bundle id, stores the raw body, copies it to notification_forward_url when set, and applies it.

  • 200: handled, including notifications about purchases this server has not seen (stored; applied only with track_new_purchases).
  • 400: the payload cannot be verified or belongs to another app. App Store Connect shows it as failed.
  • 404: no App Store app with this id.
  • 500: RevenueDot failed; Apple retries.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (application/json)

Field Type Required Description
signedPayload string yes Apple's signed JWS.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/apple/$APP_ID"

Responses

  • 200: Handled.
  • 400: Unverifiable.
  • 404: Unknown app.
  • 500: Failed; Apple retries.

Example 200 response:

JSON
{
  "ok": true
}

Google Play real-time developer notifications (Pub/Sub push)#

POST /v1/notifications/google/{app_id} · Auth: Pub/Sub push token (optional)

Set this URL as the endpoint of a Pub/Sub push subscription on the topic Google Play publishes to. When the app's pubsub_audience credential is set, the push must carry a Google-signed OIDC token for that audience (and for pubsub_service_account when set). Each message is stored once (by message id), forwarded when notification_forward_url is set, and applied by reading the purchase from the Play Developer API.

  • 200: handled, a duplicate, ignored (another package, not a developer notification) or an invalid token that can never succeed.
  • 400: not a Pub/Sub push body. 401: bad push token. 404: no Google Play app with this id.
  • 500 or 503: a temporary failure; Pub/Sub redelivers.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (application/json)

Field Type Required Description
message object yes
message.data string no Base64 JSON developer notification.
message.messageId string no
message.publishTime string no
subscription string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/google/$APP_ID"

Responses

  • 200: Handled.
  • 400: Not a push body. Returns V1Error.
  • 401: Bad push token. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure; Pub/Sub retries. Returns V1Error.
  • 503: Google's signing keys could not be loaded. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed"
}

Response signing#

Trusted Entitlements: the public key responses are signed with.

Public key for response signatures#

GET /.well-known/revenuedot-signing-key · Auth: none

The Ed25519 root public key this server signs SDK responses with (Trusted Entitlements). Pin this key in SDK builds that verify responses. The server signs only when REVENUEDOT_SIGNING_KEY is set; otherwise this answers 404. See Trusted Entitlements.

Example request

Shell
curl -s "$REVENUEDOT_URL/.well-known/revenuedot-signing-key"

Responses

  • 200: The key.
  • 404: Signing is off. Returns V1Error.

Example 200 response:

JSON
{
  "algorithm": "Ed25519",
  "public_key": "ZzwPxGlon0E8ErpDh9QAH0Jh6+E6D6qufvTSetXZY9Y=",
  "encoding": "base64",
  "header": "X-Signature",
  "docs": "https://revenuedot.app/docs"
}

Objects#

The shapes the operations above send and return.

CustomerInfo#

Field Type Required Description
request_date string yes Server time of the response. ISO 8601 in UTC, whole seconds.
request_date_ms integer yes Server time of the response. Epoch milliseconds.
subscriber object yes
subscriber.entitlements object yes Entitlements the customer has now or had, keyed by lookup key. Check expires_date (or use the SDK's isActive).
subscriber.first_seen string yes When the customer was first seen. ISO 8601 in UTC, whole seconds.
subscriber.last_seen string yes When the customer was last seen. ISO 8601 in UTC, whole seconds.
subscriber.management_url string or null no Always null today.
subscriber.non_subscriptions object yes One-time purchases by product id, oldest first.
subscriber.original_app_user_id string yes The customer's first app user id.
subscriber.original_application_version string or null no Always null today.
subscriber.original_purchase_date string or null no Earliest purchase. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
subscriber.other_purchases object no Always empty.
subscriber.subscriber_attributes object no Only in answers to secret-key requests.
subscriber.subscriptions object yes Latest subscription per product id.

EntitlementInfo#

Field Type Required Description
expires_date string or null yes When access ends. Null for lifetime access. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
grace_period_expires_date string or null yes End of the billing grace period, when the store grants one. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
product_identifier string yes Store product id that gives this access.
product_plan_identifier string no Google Play base plan id, only when there is one.
purchase_date string yes Start of the current period. ISO 8601 in UTC, whole seconds.

NonSubscriptionInfo#

Field Type Required Description
display_name string or null no
id string yes RevenueDot purchase id.
is_sandbox boolean yes
original_purchase_date string or null no Purchase time. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
purchase_date string or null yes Purchase time. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
store string yes
store_transaction_id string yes Store transaction id.
price object no
price.amount number yes Price in the purchase currency.
price.currency string yes ISO 4217 currency code.

Offerings#

Field Type Required Description
current_offering_id string or null yes Lookup key of the current offering, or the customer's override.
offerings array of object yes
offerings[].description string yes Offering display name.
offerings[].identifier string yes Offering lookup key.
offerings[].metadata object or null yes Your JSON metadata.
offerings[].packages array of object yes
offerings[].packages[].identifier string yes Package lookup key, for example $rc_monthly.
offerings[].packages[].platform_product_identifier string yes Store product id for the calling app.
offerings[].packages[].platform_product_plan_identifier string no Google Play base plan id, when the product is subscription:base-plan.

ReceiptResponse#

Field Type Required Description
request_date string yes Server time of the response. ISO 8601 in UTC, whole seconds.
request_date_ms integer yes Server time of the response. Epoch milliseconds.
subscriber object yes
subscriber.entitlements object yes Entitlements the customer has now or had, keyed by lookup key. Check expires_date (or use the SDK's isActive).
subscriber.first_seen string yes When the customer was first seen. ISO 8601 in UTC, whole seconds.
subscriber.last_seen string yes When the customer was last seen. ISO 8601 in UTC, whole seconds.
subscriber.management_url string or null no Always null today.
subscriber.non_subscriptions object yes One-time purchases by product id, oldest first.
subscriber.original_app_user_id string yes The customer's first app user id.
subscriber.original_application_version string or null no Always null today.
subscriber.original_purchase_date string or null no Earliest purchase. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
subscriber.other_purchases object no Always empty.
subscriber.subscriber_attributes object no Only in answers to secret-key requests.
subscriber.subscriptions object yes Latest subscription per product id.
purchased_products object no One entry per purchase the receipt contained.

SubscriptionInfo#

Field Type Required Description
auto_resume_date string or null no When a paused Google Play subscription resumes. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
billing_issues_detected_at string or null no When the latest renewal failed. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
display_name string or null no Product display name.
expires_date string or null yes End of the current period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
grace_period_expires_date string or null no End of the billing grace period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
is_sandbox boolean yes True for sandbox and Test Store purchases.
management_url string or null no Always null today.
original_purchase_date string or null no Start of the subscription. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
ownership_type PURCHASED, FAMILY_SHARED no
period_type normal, trial, intro, promotional, prepaid yes
purchase_date string or null yes Start of the current period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
refunded_at string or null no When the store refunded it. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
store app_store, mac_app_store, play_store, amazon, stripe, rc_billing, promotional, test_store, paddle, roku, external yes
store_transaction_id string or null no Latest store transaction id (Apple), order id (Google) or Test Store token.
unsubscribe_detected_at string or null no When auto-renew was turned off. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
product_plan_identifier string no Google Play base plan id, only when there is one.
price object no
price.amount number yes Price in the purchase currency.
price.currency string yes ISO 4217 currency code.

V1Error#

Field Type Required Description
code integer yes RevenueCat-compatible backend error code. See the error table.
message string yes What went wrong.
attribute_errors array of object no Only for 7263.
attribute_errors[].key_name string no
attribute_errors[].message string no