Server-side reference for the RevenueCat REST API v2: endpoint map, sk_/atk_ auth, pagination, expandables, rate limits, error types, and write-safety. Symlinked into ~/.claude/skills and listed in README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
| name | description |
|---|---|
| revenuecat-api-v2 | 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:
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:
Authorization: Bearer <token>
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:
Content-Type: application/json
Always URL-encode path params such as customer_id.
Use environment filters where relevant:
environment=sandbox
environment=production
Do not infer production state from sandbox data.
Core objects
RevenueCat v2 is project-centred.
Common object hierarchy:
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
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
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
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
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
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
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
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
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
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
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:
{
"object": "list",
"items": [],
"next_page": "...",
"url": "..."
}
Use:
limit, default20starting_afternext_pageuntil absent
Pagination is forward-only.
Expandables
Use expand when an endpoint supports it to fetch related objects in one request.
Example:
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-UsageRevenueCat-Rate-Limit-Current-LimitRetry-Afteron429
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: success4xx: client/auth/validation/conflict/rate-limit issue5xx: RevenueCat/upstream issue
Error bodies include fields like:
{
"type": "parameter_error",
"param": "customer_id",
"message": "id is too long",
"retryable": false,
"doc_url": "..."
}
Common error types:
authentication_error: bad tokenauthorization_error: token lacks access to project/permissioninvalid_request: wrong method or missing JSON content typeparameter_error: invalid path/query/body valuerate_limit_error: wait usingbackoff_ms/Retry-Afterresource_missing: bad ID or deleted resourceresource_already_exists: duplicate IDresource_locked_error: concurrent modificationstore_error: upstream app store problemunprocessable_entity_error: valid syntax, impossible operationentity_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
- Fetch the customer:
GET /projects/{project_id}/customers/{customer_id}
- Fetch active entitlements:
GET /projects/{project_id}/customers/{customer_id}/active_entitlements
- Fetch subscriptions if detail is needed:
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
GET /projects/{project_id}/customers?search=user@example.com
This searches exact $email attribute matches.
Set customer attributes
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
POST /projects/{project_id}/customers/{customer_id}/actions/grant_entitlement
Explain that this creates a promotional subscription.
Revoke granted access
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:
- apps
- products
- entitlements
- entitlement product attachments
- offerings
- packages
- package product attachments
- paywalls
Use expand where supported.
Configure webhooks
Create or update:
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
curlor 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
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:
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/..."