Merchant API
Server-to-server REST · Secret API keys (rc_sk_) · Stripe-style objects · No raw card data
Summary
The RapidCents Merchant API v1 is a server-to-server REST API for managing customers, products, prices, subscriptions, refunds, and voids. Authenticate with a secret API key (rc_sk_…). The business is resolved from the key and is never accepted from the request body.
No raw card data on this API. Payment methods are stored cards collected via hosted checkout or the dashboard. Requests that include PAN, CVC, or similar fields are rejected with raw_card_data_not_supported.
Integration Flow
Typical recurring billing flow with the Merchant API:
- Create a customer —
POST /api/v1/customerswith name and/or email. - Create product & recurring price — Catalog items first; amounts live on prices (cents).
- Create a subscription — Pass
customer, recurringprice, and optionally a storedpayment_method. - Collect payment method if needed — Without a stored card, RapidCents returns a hosted
checkout_url. - Reconcile via webhooks & payments API — Listen for subscription/payment events; list payments under the subscription.
API Key Authentication
All Merchant API endpoints require a secret API key in the Authorization header. Keys are created in the RapidCents dashboard and start with rc_sk_.
Required Headers
Authorization: Bearer rc_sk_…
Content-Type: application/json
Accept: application/json
Base URL Pattern
All Merchant API endpoints are under /api/v1/…. Business scope comes from the key — do not send business_id in the body.
Keep secret keys on the server only. Never ship rc_sk_ keys to browsers, mobile apps, or public repos. Rotate compromised keys immediately from the dashboard.
Smoke Test
GET /api/v1/account
{
"data": {
"object": "account",
"business_id": "biz-uuid",
"business_name": "Acme Store",
"api_key_id": "key-uuid",
"api_key_name": "Production",
"api_key_last_four": "a1b2"
}
}
IP Whitelisting
Required: Each API key must be activated with at least one allowed IP. Requests from non-allowlisted IPs are rejected.
Configure allowed_ips when creating or rotating keys in the dashboard (1–2 IPs per key). Staging and production keys have separate allowlists.
| Environment | Base URL | Whitelist |
|---|---|---|
| Staging | https://uatstage00-api.rapidcents.com |
Separate — add your staging server IPs |
| Production | https://api.rapidcents.com |
Separate — add your production server IPs |
Rate Limits
Limits are enforced per API key / business. Exceeding a limit returns 429 with rate_limit_error.
| Scope | Default |
|---|---|
| Overall | 60 requests / minute |
| Subscription create | 20 / minute |
| Refunds | 5 / minute |
| Voids | 5 / minute |
| Invalid-key attempts (per IP) | 10 / minute |
HTTP Idempotency-Key support is intentionally deferred to a later phase. Design writes so retries are safe (e.g. customer create is idempotent by email).
Account
GET /api/v1/account
Returns safe identifiers for the authenticated key and its business. Use this to verify credentials after onboarding.
Customers
Create a Customer
POST /api/v1/customers
POST /api/v1/customers
Authorization: Bearer rc_sk_…
Content-Type: application/json
{
"name": "Jane Doe",
"email": "[email protected]",
"phone": "+1-555-0100",
"metadata": { "crm_id": "123" }
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Conditional | Max 255. Either name or email is required. |
email | string | Conditional | Valid email. Used for firstOrCreate dedupe. |
phone | string | No | Max 50. |
metadata | object | No | Arbitrary key/value map. |
{
"id": "cust-uuid",
"object": "customer",
"name": "Jane Doe",
"email": "[email protected]",
"phone": "+1-555-0100",
"metadata": { "crm_id": "123" },
"created": 1710000000
}
List / Show / Update
| Method | Endpoint | Notes |
|---|---|---|
| GET | /api/v1/customers | Cursor pagination |
| GET | /api/v1/customers/{id} | Single customer |
| PATCH | /api/v1/customers/{id} | Same fields as create (sometimes) |
Payment Methods
Read and delete stored cards. Raw card create is not supported on Merchant API v1.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/payment_methods | List; optional ?customer= |
| GET | /api/v1/customers/{id}/payment_methods | List for one customer |
| GET | /api/v1/payment_methods/{id} | Show one |
| DELETE | /api/v1/payment_methods/{id} | Soft-detach (on_file=false) |
{
"id": "pm-uuid",
"object": "payment_method",
"type": "card",
"customer": "cust-uuid",
"card": {
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2030
},
"status": "active",
"created": 1710000000
}
Collect cards via hosted subscription checkout or the dashboard. Sending card, cvc, pan, etc. returns 422 raw_card_data_not_supported.
Products
POST /api/v1/products
{
"name": "Pro Plan",
"description": "Monthly subscription",
"active": true,
"metadata": {}
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Max 255 |
description | string | No | Max 2000 |
active | boolean | No | Default true |
metadata | object | No | Arbitrary map |
Also: GET /products, GET /products/{id}, PATCH /products/{id}. List filter: ?active=true|false.
Amounts live on Prices, not Products. Creating a product does not charge anything.
Prices
POST /api/v1/prices
{
"product": "prod-uuid",
"unit_amount": 2999,
"recurring": {
"interval": "month",
"interval_count": 1
},
"nickname": "Pro Monthly",
"active": true
}
| Field | Type | Required | Description |
|---|---|---|---|
product | string | Yes | Product id |
unit_amount | integer | Yes | Amount in cents (≥ 1) |
recurring.interval | string | For recurring | day | week | month | year |
recurring.interval_count | integer | No | Default 1 |
nickname | string | No | Display label |
active | boolean | No | Default true |
metadata | object | No | Arbitrary map |
Supported cadences
day:1, week:1, week:2, month:1, month:3, month:6, year:1
Amount, currency, and recurring cadence are immutable after create. PATCH only allows nickname, active, and metadata.
Subscriptions
POST /api/v1/subscriptions
{
"customer": "cust-uuid",
"price": "price-uuid",
"payment_method": "pm-uuid",
"start_date": "2026-08-01",
"tax_rate": 13,
"surcharge_rate": 2.4
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
customer | string | Yes | Customer id |
price | string | Yes | Must be recurring + active |
payment_method | string | No | Stored card id — never raw PAN |
start_date | date | No | Defaults to today |
installments | integer | No | ≥ 1 if set |
surcharge_rate | number | No | Human % 0–2.4 (e.g. 2.4) |
tax_rate | number | No | Human % 0–30 (e.g. 13) |
send_payment_link | boolean | No | Default true when no PM |
active
Billing is live; first charge succeeded or card on file.
pending_payment_method
Hosted checkout_url returned; waiting for card.
completed
Installment plan finished all bills.
canceled
Cancelled via DELETE /subscriptions/{id}.
Create behavior
- With
payment_method— Attempts first charge. Decline deletes the subscription and returns 402. - Without
payment_method— Creates inactive subscription + hosted link; optionally emails the customer. - Emits webhook
subscription.created.
Cancel
DELETE /api/v1/subscriptions/{id}
Deactivates the subscription, clears next billing, unfulfills pending queue rows, and emits subscription.cancelled.
List filters
?customer= and ?status=active|canceled|pending_payment_method (aliases: cancelled, incomplete).
Subscription Payments
Read-only history of sales for a subscription.
| Method | Endpoint |
|---|---|
| GET | /api/v1/subscriptions/{id}/payments |
| GET | /api/v1/subscriptions/{id}/payments/{payment} |
{
"id": "pay-uuid",
"object": "payment",
"subscription": "sub-uuid",
"amount": 2999,
"currency": "CAD",
"status": "succeeded",
"refunded_amount": 0,
"created": 1710000000
}
Statuses: succeeded, failed, refunded, voided. Refund ledger rows are excluded; use refunded_amount on the original payment.
Refunds
POST /api/v1/refunds
{
"payment": "pay-uuid",
"amount": 1000
}
| Field | Type | Required | Description |
|---|---|---|---|
payment | string | Yes | Subscription History sale id |
amount | integer | No | Cents; omit for full remaining balance |
- Subscription payments only (not VT / payment-link)
- Webhooks:
payment.refundedorpayment.partially_refunded - Customer email: refund notification
- Show: GET
/api/v1/refunds/{id}
Voids
POST /api/v1/voids
{
"payment": "pay-uuid"
}
Voids an open-batch subscription payment. Returns the updated payment object with status: "voided" and emits payment.voided.
If the batch is already settled, use a refund instead of a void.
All Endpoints
Account
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/v1/account | API key | Auth smoke test |
Customers
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/customers | API key | Create customer |
| GET | /api/v1/customers | API key | List customers |
| GET | /api/v1/customers/{id} | API key | Retrieve customer |
| PATCH | /api/v1/customers/{id} | API key | Update customer |
Payment Methods
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/v1/payment_methods | API key | List stored cards |
| GET | /api/v1/customers/{id}/payment_methods | API key | List by customer |
| GET | /api/v1/payment_methods/{id} | API key | Retrieve card |
| DELETE | /api/v1/payment_methods/{id} | API key | Detach card |
Products & Prices
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/products | API key | Create product |
| GET | /api/v1/products | API key | List products |
| GET | /api/v1/products/{id} | API key | Retrieve product |
| PATCH | /api/v1/products/{id} | API key | Update product |
| POST | /api/v1/prices | API key | Create price |
| GET | /api/v1/prices | API key | List prices |
| GET | /api/v1/prices/{id} | API key | Retrieve price |
| PATCH | /api/v1/prices/{id} | API key | Update price metadata |
Subscriptions & Payments
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/subscriptions | API key | Create subscription |
| GET | /api/v1/subscriptions | API key | List subscriptions |
| GET | /api/v1/subscriptions/{id} | API key | Retrieve subscription |
| DELETE | /api/v1/subscriptions/{id} | API key | Cancel subscription |
| GET | /api/v1/subscriptions/{id}/payments | API key | List payments |
| GET | /api/v1/subscriptions/{id}/payments/{payment} | API key | Retrieve payment |
Refunds & Voids
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/refunds | API key | Create refund |
| GET | /api/v1/refunds/{id} | API key | Retrieve refund |
| POST | /api/v1/voids | API key | Void open-batch payment |
Pagination
List endpoints return a Stripe-style list envelope with cursor pagination:
{
"object": "list",
"data": [ … ],
"has_more": false
}
| Param | Rules |
|---|---|
limit | 1–100, default 10 |
starting_after | Resource UUID — older page |
ending_before | Resource UUID — newer page |
Order: created_at DESC, id tiebreaker.
Environment Configuration
| Environment | Base URL | Purpose |
|---|---|---|
| Staging | https://uatstage00-api.rapidcents.com |
Development, testing, and integration |
| Production | https://api.rapidcents.com |
Live transactions |
Each environment has separate API keys and IP allowlists. Do not use staging keys in production.
Error Handling
Errors use a consistent envelope (not the dashboard { ok, status } shape):
{
"error": {
"type": "validation_error",
"code": "parameter_invalid",
"message": "The email must be a valid email address.",
"param": "email"
}
}
| HTTP | Type | Examples |
|---|---|---|
| 401 | authentication_error | invalid_api_key |
| 402 | card_error | card_declined |
| 403 | permission_error | permission_denied |
| 404 | invalid_request_error | resource_missing |
| 409 | conflict_error | conflict |
| 422 | validation_error | parameter_invalid, raw_card_data_not_supported |
| 429 | rate_limit_error | rate_limit_exceeded |
| 500 | api_error | internal_error |
Security Best Practices
- Secret keys stay on the server — Never expose
rc_sk_keys in frontend code or mobile apps. - Use HTTPS exclusively — All API traffic must use TLS.
- Whitelist server IPs — Activate keys with allowlisted outbound IPs; update before infrastructure changes.
- Never send raw card data — Collect cards via hosted checkout; use stored payment method IDs only.
- Money in cents — Send integer minor units for amounts; tax/surcharge rates as human percentages.
- Verify webhooks — Treat webhook deliveries as the source of truth for payment state changes.
- Rotate compromised keys — Revoke and reissue from the dashboard immediately.
Integration Checklist
Complete all items before going live with production transactions.
Authentication & Setup
- Secret API key created and stored in a secrets manager
- Server IP(s) allowlisted and key activated
GET /api/v1/accountsucceeds from your server- Staging and production keys stored separately
Catalog & Customers
- Products and recurring prices created with amounts in cents
- Customer create/update/list wired to your CRM
- Email conflict / firstOrCreate behavior understood
Subscriptions
- Create with stored
payment_methodpath tested (including 402 declines) - Create without PM returns
checkout_url; hosted flow completes - Cancel path tested; pending queue rows cleared
- Webhooks for
subscription.created/subscription.cancelledhandled
Refunds & Voids
- Full and partial refunds tested on subscription payments
- Void only used for open-batch payments
payment.refunded/payment.voidedwebhooks handled