--- name: revenuecat-api-v2 description: Use when integrating, inspecting, automating, or debugging the RevenueCat REST API v2 server-side — reading customer subscription/entitlement/access state, creating or updating customers and attributes, granting or revoking promotional access, managing products, entitlements, offerings, packages, paywalls, apps, or webhooks, and handling refunds, cancellations, metrics, audit logs, or receipt/state migrations. Covers the v2 endpoint map, sk_/atk_ auth, pagination, expandables, rate limits, error types, and write-safety. Server-side REST, not the client SDK. --- # RevenueCat API v2 Skill Use this skill when integrating, inspecting, automating, or debugging RevenueCat REST API v2. ## Source of truth Use the current official RevenueCat API v2 docs before implementing. API v2 is still under development and does not cover every v1 use case. Use v1 only when v2 lacks the required endpoint. Base URL: ```text https://api.revenuecat.com/v2 ``` ## When to use SDK vs REST Use the RevenueCat SDK for normal client purchase flows. Use REST API v2 for server-side work: - reading customer subscription/access state - creating or updating customers and attributes - granting or revoking promotional access - managing products, entitlements, offerings, packages, paywalls, apps, webhooks - refunds, cancellations, subscription management URLs - metrics, audit logs, invoices, virtual currencies - migrations where the backend already has receipts or user state ## Authentication Every request uses: ```http Authorization: Bearer ``` Valid tokens: - `sk_...` secret API key: server-to-server, project-scoped. - `atk_...` OAuth access token: third-party tools, developer-scoped. - Public SDK keys are for SDK/client configuration only. Never use secret keys in an app, website, repo, or frontend. API v1 keys do not work with API v2. Create a v2 secret key and assign only the endpoint permissions needed. ## Required request habits For `POST`, `PUT`, and `PATCH`: ```http Content-Type: application/json ``` Always URL-encode path params such as `customer_id`. Use environment filters where relevant: ```text environment=sandbox environment=production ``` Do not infer production state from sandbox data. ## Core objects RevenueCat v2 is project-centred. Common object hierarchy: ```text Project Apps Products Entitlements Offerings Packages Products Customers Attributes Aliases Active entitlements Subscriptions Purchases Invoices Virtual currencies Paywalls Webhook integrations Metrics / audit logs ``` Use `project_id` for almost every v2 endpoint. ## Endpoint map ### Projects and apps ```text GET /projects POST /projects GET /projects/{project_id}/apps POST /projects/{project_id}/apps GET /projects/{project_id}/apps/{app_id} POST /projects/{project_id}/apps/{app_id} DEL /projects/{project_id}/apps/{app_id} GET /projects/{project_id}/apps/{app_id}/api_keys GET /projects/{project_id}/apps/{app_id}/store_kit_config ``` ### Customers ```text GET /projects/{project_id}/customers POST /projects/{project_id}/customers GET /projects/{project_id}/customers/{customer_id} DEL /projects/{project_id}/customers/{customer_id} GET /projects/{project_id}/customers/{customer_id}/active_entitlements GET /projects/{project_id}/customers/{customer_id}/subscriptions GET /projects/{project_id}/customers/{customer_id}/purchases GET /projects/{project_id}/customers/{customer_id}/aliases GET /projects/{project_id}/customers/{customer_id}/attributes POST /projects/{project_id}/customers/{customer_id}/attributes ``` Customer search currently supports exact email matching via `$email`. ### Customer actions ```text POST /projects/{project_id}/customers/{customer_id}/actions/transfer POST /projects/{project_id}/customers/{customer_id}/actions/grant_entitlement POST /projects/{project_id}/customers/{customer_id}/actions/revoke_granted_entitlement POST /projects/{project_id}/customers/{customer_id}/actions/assign_offering POST /projects/{project_id}/customers/{customer_id}/actions/restore_purchase_by_order_id ``` Granting an entitlement creates a promotional subscription. Revoking it expires that promotional subscription. Treat both as user-impacting writes. ### Subscriptions ```text GET /projects/{project_id}/subscriptions/{subscription_id} GET /projects/{project_id}/subscriptions GET /projects/{project_id}/subscriptions/{subscription_id}/transactions GET /projects/{project_id}/subscriptions/{subscription_id}/entitlements GET /projects/{project_id}/subscriptions/{subscription_id}/authenticated_management_url POST /projects/{project_id}/subscriptions/{subscription_id}/actions/cancel POST /projects/{project_id}/subscriptions/{subscription_id}/actions/extend POST /projects/{project_id}/subscriptions/{subscription_id}/actions/refund POST /projects/{project_id}/subscriptions/{subscription_id}/transactions/{transaction_id}/actions/refund ``` Use subscription fields such as `gives_access`, `status`, `auto_renewal_status`, `pending_payment`, `current_period_ends_at`, `store_subscription_identifier`, and `total_revenue_in_usd`. Prefer `gives_access` and active entitlements over hand-rolled expiry logic. ### Purchases ```text GET /projects/{project_id}/purchases/{purchase_id} GET /projects/{project_id}/purchases/{purchase_id}/entitlements GET /projects/{project_id}/purchases POST /projects/{project_id}/purchases/{purchase_id}/actions/refund ``` Use purchase search when starting from a store purchase identifier. ### Product catalog ```text GET /projects/{project_id}/entitlements POST /projects/{project_id}/entitlements GET /projects/{project_id}/entitlements/{entitlement_id} POST /projects/{project_id}/entitlements/{entitlement_id} DEL /projects/{project_id}/entitlements/{entitlement_id} POST /projects/{project_id}/entitlements/{entitlement_id}/actions/archive POST /projects/{project_id}/entitlements/{entitlement_id}/actions/unarchive POST /projects/{project_id}/entitlements/{entitlement_id}/actions/attach_products POST /projects/{project_id}/entitlements/{entitlement_id}/actions/detach_products GET /projects/{project_id}/products POST /projects/{project_id}/products GET /projects/{project_id}/products/{product_id} POST /projects/{project_id}/products/{product_id} DEL /projects/{project_id}/products/{product_id} POST /projects/{project_id}/products/{product_id}/actions/archive POST /projects/{project_id}/products/{product_id}/actions/unarchive POST /projects/{project_id}/products/{product_id}/create_in_store ``` Product creation does not create Web Billing products. Store push has store-specific requirements. ### Offerings and packages ```text GET /projects/{project_id}/offerings POST /projects/{project_id}/offerings GET /projects/{project_id}/offerings/{offering_id} POST /projects/{project_id}/offerings/{offering_id} DEL /projects/{project_id}/offerings/{offering_id} GET /projects/{project_id}/offerings/{offering_id}/packages POST /projects/{project_id}/offerings/{offering_id}/packages GET /projects/{project_id}/offerings/{offering_id}/packages/{package_id} POST /projects/{project_id}/offerings/{offering_id}/packages/{package_id} DEL /projects/{project_id}/offerings/{offering_id}/packages/{package_id} POST /projects/{project_id}/offerings/{offering_id}/packages/{package_id}/actions/attach_products POST /projects/{project_id}/offerings/{offering_id}/packages/{package_id}/actions/detach_products ``` Deleting an offering also deletes attached packages. ### Paywalls ```text POST /projects/{project_id}/paywalls/media GET /projects/{project_id}/paywalls POST /projects/{project_id}/paywalls GET /projects/{project_id}/paywalls/{paywall_id} PATCH /projects/{project_id}/paywalls/{paywall_id} DEL /projects/{project_id}/paywalls/{paywall_id} ``` Paywall updates affect drafts. ### Webhooks ```text GET /projects/{project_id}/integrations/webhooks POST /projects/{project_id}/integrations/webhooks GET /projects/{project_id}/integrations/webhooks/{webhook_integration_id} POST /projects/{project_id}/integrations/webhooks/{webhook_integration_id} DEL /projects/{project_id}/integrations/webhooks/{webhook_integration_id} ``` Webhook fields include name, URL, optional authorization header, environment, event types, and optional app scope. ### Metrics and audit ```text GET /projects/{project_id}/metrics/overview GET /projects/{project_id}/metrics/revenue GET /projects/{project_id}/metrics/charts/{chart_id} GET /projects/{project_id}/metrics/charts/{chart_id}/options GET /projects/{project_id}/audit_logs GET /projects/{project_id}/collaborators ``` Metrics have stricter rate limits than customer reads. ## Pagination List responses use: ```json { "object": "list", "items": [], "next_page": "...", "url": "..." } ``` Use: - `limit`, default `20` - `starting_after` - `next_page` until absent Pagination is forward-only. ## Expandables Use `expand` when an endpoint supports it to fetch related objects in one request. Example: ```text GET /projects/{project_id}/customers/{customer_id}?expand=attributes ``` Only request expansions the API key has permission to read. ## Rate limits Default domains: - Customer Information: 480 requests/minute - Charts & Metrics: 25 requests/minute - Project Configuration: 60 requests/minute - Virtual Currency transaction creation: 480 requests/minute Read these headers: - `RevenueCat-Rate-Limit-Current-Usage` - `RevenueCat-Rate-Limit-Current-Limit` - `Retry-After` on `429` Back off on `429`, `423`, retryable `5xx`, and any response with `retryable: true` or `backoff_ms`. ## Error handling RevenueCat uses normal HTTP status classes: - `2xx`: success - `4xx`: client/auth/validation/conflict/rate-limit issue - `5xx`: RevenueCat/upstream issue Error bodies include fields like: ```json { "type": "parameter_error", "param": "customer_id", "message": "id is too long", "retryable": false, "doc_url": "..." } ``` Common error types: - `authentication_error`: bad token - `authorization_error`: token lacks access to project/permission - `invalid_request`: wrong method or missing JSON content type - `parameter_error`: invalid path/query/body value - `rate_limit_error`: wait using `backoff_ms` / `Retry-After` - `resource_missing`: bad ID or deleted resource - `resource_already_exists`: duplicate ID - `resource_locked_error`: concurrent modification - `store_error`: upstream app store problem - `unprocessable_entity_error`: valid syntax, impossible operation - `entity_references_archived_entities`: unarchive referenced objects first ## Write safety Before any write, state: - method and endpoint - required permission - affected object IDs - side effects - whether it touches production or sandbox Do not perform destructive/user-impacting writes unless explicitly authorized. Treat these as high-risk: - deleting customers, apps, products, offerings, paywalls, webhooks - granting/revoking entitlements - transferring purchases/subscriptions - refunding purchases/subscriptions - cancelling/extending subscriptions - changing production product catalog - changing production webhooks For uncertain writes, provide a dry-run request and explain expected result. ## Common workflows ### Check whether a customer has access 1. Fetch the customer: ```text GET /projects/{project_id}/customers/{customer_id} ``` 2. Fetch active entitlements: ```text GET /projects/{project_id}/customers/{customer_id}/active_entitlements ``` 3. Fetch subscriptions if detail is needed: ```text GET /projects/{project_id}/customers/{customer_id}/subscriptions?environment=production ``` Use `active_entitlements` and subscription `gives_access`. Do not invent access rules from expiry dates alone. ### Find a customer by email ```text GET /projects/{project_id}/customers?search=user@example.com ``` This searches exact `$email` attribute matches. ### Set customer attributes ```text POST /projects/{project_id}/customers/{customer_id}/attributes Content-Type: application/json ``` Use reserved attribute names like `$email` only according to RevenueCat’s rules. Keep custom attribute names short and stable. ### Grant temporary access ```text POST /projects/{project_id}/customers/{customer_id}/actions/grant_entitlement ``` Explain that this creates a promotional subscription. ### Revoke granted access ```text POST /projects/{project_id}/customers/{customer_id}/actions/revoke_granted_entitlement ``` Explain that this expires the promotional subscription. ### Build a product catalogue view Read in this order: 1. apps 2. products 3. entitlements 4. entitlement product attachments 5. offerings 6. packages 7. package product attachments 8. paywalls Use `expand` where supported. ### Configure webhooks Create or update: ```text POST /projects/{project_id}/integrations/webhooks POST /projects/{project_id}/integrations/webhooks/{webhook_integration_id} ``` Always specify environment intentionally. Use an authorization header for webhook verification. Do not log webhook secrets. ### Refund or cancel Use the endpoint that matches the store and object: - Web Billing purchase refund - Web Billing subscription cancel/refund/extend - Play Store or Galaxy subscription transaction refund - subscription management URL for customer-managed changes Always confirm production environment and affected subscription/purchase ID. ## Response standards When answering RevenueCat API questions: - identify SDK vs REST - identify v1 vs v2 - state endpoint, method, and required token type - state required permission scope when relevant - show minimal `curl` or code using env vars - never expose real tokens - call out side effects - explain retry/backoff behaviour - convert timestamps to readable ISO dates when shown - distinguish sandbox from production - mention when v1 is still required ## Minimal curl pattern ```bash curl -sS \ -H "Authorization: Bearer $REVENUECAT_API_KEY" \ -H "Content-Type: application/json" \ "https://api.revenuecat.com/v2/projects/$REVENUECAT_PROJECT_ID/customers/$CUSTOMER_ID" ``` For writes: ```bash curl -sS -X POST \ -H "Authorization: Bearer $REVENUECAT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"key":"value"}' \ "https://api.revenuecat.com/v2/projects/$REVENUECAT_PROJECT_ID/..." ```