Developer documentationDocsGet API keys

API reference

Every endpoint, with the request and response you can expect. All examples use test keys; swap in a live key and the same calls move real money.

Customers

The people who pay you. Created automatically at checkout, or by hand.

POST/v1/portal/sessionsscope: customers:write

A customer-portal link for a customer who is signed in to your product — redirect them to url and they can update their card, see payments, cancel or pause (as your portal settings allow). Valid for an hour. Pass return_url (http or https) and the portal shows a "Back to" link to it. Secret key only.

Request
{ "customer": "cus_1b4c…", "return_url": "https://yourapp.am/account" }
Response
{
  "object": "portal_session",
  "customer": "cus_1b4c…",
  "url": "https://ronda.am/portal/session/…",
  "return_url": "https://yourapp.am/account",
  "expires_at": 1790361234,
  "mode": "test"
}
POST/v1/customersscope: customers:write

Create a customer

Request
{
  "email": "anna@example.am",
  "name": "Anna Hakobyan",
  "metadata": { "crm_id": "8891" }
}
Response
{
  "id": "cus_1b4cf2ceec9356f1e53a3cab",
  "object": "customer",
  "email": "anna@example.am",
  "name": "Anna Hakobyan",
  "credit_balances": [],
  "metadata": { "crm_id": "8891" },
  "created": 1786998562
}
GET/v1/customersscope: customers:read

List customers — supports ?email=

GET/v1/customers/:idscope: customers:read

Retrieve a customer

POST/v1/customers/:idscope: customers:write

Update a customer — including default_payment_method (pm_…), the card renewals fall back to when a subscription's own card is removed

DELETE/v1/customers/:idscope: customers:write

Delete a customer — clears personal data, deactivates saved cards, keeps the financial record

Products

What you sell. A product owns its prices, so one product can be sold monthly and yearly without pretending to be two unrelated things.

POST/v1/productsscope: plans:write

Create a product

Request
{
  "name": "Pro",
  "description": "For growing teams"
}
Response
{
  "id": "prod_6f21…",
  "object": "product",
  "name": "Pro",
  "description": "For growing teams",
  "active": true,
  "created": 1786998563
}
GET/v1/productsscope: plans:read

List products

GET/v1/products/:idscope: plans:read

Retrieve a product

POST/v1/products/:idscope: plans:write

Update a product

DELETE/v1/products/:idscope: plans:write

Archive a product and every price under it. Existing subscriptions keep billing.

Prices (plans)

An amount and an interval, belonging to a product. Changing a price's amount applies to its existing subscribers from their next renewal — never to a period already paid. Omit `product` and the `name` creates one, so selling something takes a single call.

POST/v1/plansscope: plans:write

Create a price

Request
{
  "product": "prod_6f21…",
  "name": "Monthly",
  "amount": 12900,
  "currency": "AMD",
  "interval": "month",
  "interval_count": 1,
  "trial_days": 14
}
Response
{
  "id": "plan_3911175476653e621be07025",
  "object": "plan",
  "product": "prod_6f21…",
  "product_name": "Pro",
  "nickname": "Monthly",
  "name": "Pro — Monthly",
  "amount": 12900,
  "currency": "AMD",
  "interval": "month",
  "interval_count": 1,
  "trial_days": 14,
  "active": true,
  "created": 1786998563
}
GET/v1/plansscope: plans:read

List prices

GET/v1/plans/:idscope: plans:read

Retrieve a price

POST/v1/plans/:idscope: plans:write

Update or archive a price

Subscriptions

The recurring core. A customer, a plan, a saved card and a schedule.

POST/v1/subscriptionsscope: subscriptions:write

Create a subscription. Without a trial, a payment method is required and the first period is charged now. The response carries that charge as latest_payment. If it was declined the subscription is past_due and the recovery ladder retries it; latest_payment.failure_code says why. Statuses: trialing, active, past_due, paused, canceled — and incomplete, briefly, while the first charge is in flight (subscription.created can carry it).

Request
{
  "customer": "cus_1b4c…",
  "plan": "plan_3911…",
  "payment_method": "pm_77a2…",
  "quantity": 1
}
Response
{
  "id": "sub_5c1e…",
  "object": "subscription",
  "status": "active",
  "plan": "plan_3911…",
  "quantity": 1,
  "current_period_start": 1786998600,
  "current_period_end": 1789590600,
  "trial_end": null,
  "cancel_at_period_end": false,
  "cancellation_details": null,
  "pause": null,
  "dunning_attempt": 0,
  "latest_payment": { "id": "pay_a348…", "object": "payment", "status": "succeeded", "amount": 12900, "currency": "AMD", "failure_code": null, "failure_message": null, "…": "the full payment object" }
}
GET/v1/subscriptionsscope: subscriptions:read

List subscriptions — supports ?status= and ?customer=

GET/v1/subscriptions/:idscope: subscriptions:read

Retrieve a subscription

POST/v1/subscriptions/:id/change_planscope: subscriptions:write

Change plan with proration. An upgrade charges the difference now; a downgrade credits the customer.

Request
{ "plan": "plan_bigger…", "prorate": true }
Response
{
  "id": "sub_5c1e…",
  "status": "active",
  "plan": "plan_bigger…",
  "proration": { "charged": 19677, "credited": 0, "currency": "AMD", "payment": "pay_aa31…" }
}
POST/v1/subscriptions/:id/cancelscope: subscriptions:write

Cancel now, or at period end with { "at_period_end": true }. Optionally record why: reason is one of too_expensive, not_using, missing_feature, switching, temporary, other, plus a free-text comment. They come back in cancellation_details, with canceled_by: merchant, customer (from the portal) or payment_failed.

Request
{ "at_period_end": true, "reason": "switching", "comment": "Moving to annual invoicing" }
POST/v1/subscriptions/:id/pausescope: subscriptions:write

Pause billing without canceling

POST/v1/subscriptions/:id/resumescope: subscriptions:write

Resume where it stood — a trial keeps its end, a paid period keeps its end; one that ran out while paused, or was paused owing money, is charged now

POST/v1/subscriptions/:idscope: subscriptions:write

Update quantity, metadata, the card it is charged to (payment_method — a past-due subscription is retried on it now), or withdraw a scheduled cancel ({ "cancel_at_period_end": false }). A quantity change is prorated like a plan change.

Request
{ "quantity": 3, "payment_method": "pm_77a2…", "metadata": { "seat_plan": "team" } }
POST/v1/subscriptions/:id/simulate_renewalscope: subscriptions:write

Test mode only: run the next renewal now — the trial ends, or the next period or retry is charged — through the same path as the real schedule, with the same emails and webhooks. Refused with a live key.

POST/v1/subscriptions/:id/retryscope: subscriptions:write

Past due: try the payment now instead of at next_attempt — after the customer fixed their card in your app. A decline counts as one of the retries. Up to three a day.

Payments

Every charge, one-off and recurring.

POST/v1/paymentsscope: payments:write

Charge a saved card off-session — the same call the recurring engine makes

Request
{
  "amount": 45000,
  "currency": "AMD",
  "customer": "cus_1b4c…",
  "description": "September retainer"
}
Response
{
  "id": "pay_a348cb5b0664660d4c29d890",
  "object": "payment",
  "amount": 45000,
  "currency": "AMD",
  "status": "succeeded",
  "type": "one_off",
  "card": { "brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2030 },
  "attempt": 1,
  "is_recovery": false,
  "created": 1786998900
}
GET/v1/paymentsscope: payments:read

List payments — supports ?status= and ?customer=

GET/v1/payments/:idscope: payments:read

Retrieve a payment

POST/v1/payments/:id/refundscope: refunds:write

Refund in whole or part. The platform fee on the refunded amount is reversed.

Request
{ "amount": 20000, "reason": "duplicate" }
Response
{
  "id": "re_66b1…",
  "object": "refund",
  "payment": "pay_a348…",
  "amount": 20000,
  "status": "succeeded"
}
GET/v1/refundsscope: payments:read

List refunds

GET/v1/refunds/:idscope: payments:read

Retrieve a refund

Checkout sessions

The only way a card enters the system. Works with a publishable key.

POST/v1/checkout/sessionsscope: publishable or payments:write

Open a hosted checkout. type is payment, setup or subscription. The bank's order is registered as the session is created, so it comes back already processing and stays so while the customer is on the page or at the bank; it ends completed, failed, expired or canceled. (open is only ever seen if the bank could not be reached at creation.) Send the customer to url — our page, which forwards to the bank. bank_url is the bank's own card page, for an integration that must redirect there directly; prefer url, which also shows the test banner, the amount and the result.

Request
{
  "type": "payment",
  "amount": 25000,
  "currency": "AMD",
  "description": "September membership",
  "customer_email": "anna@example.am",
  "save_card": true,
  "success_url": "https://yourapp.am/thanks"
}
Response
{
  "id": "cs_f4accee46724b73fae2ced5a",
  "object": "checkout_session",
  "status": "processing",
  "amount": 25000,
  "currency": "AMD",
  "url": "https://ronda.am/checkout/cs_f4ac…",
  "bank_url": "https://…bank…/payments/…",
  "expires_at": 1787000363
}
GET/v1/checkout/sessions/:id

Retrieve a session and its outcome

Payment methods

Saved cards. Ronda holds the bank's token, brand and last four digits — never a card number.

GET/v1/payment_methodsscope: customers:read

List saved cards — supports ?customer=

DELETE/v1/payment_methods/:idscope: customers:write

Detach a card and deactivate the binding at the bank

Invoices

The document behind a charge — and the way to bill a company that will not hand over a card. Every subscription cycle writes one; you can also raise one directly. Sent invoices carry a due date and a hosted page the customer can pay from.

POST/v1/invoicesscope: payments:write

Raise an invoice and send it

Request
{
  "customer": "cus_1b4c…",
  "currency": "AMD",
  "collection_method": "send_invoice",
  "days_until_due": 14,
  "memo": "Thank you",
  "lines": [
    { "description": "Consulting, March", "quantity": 2, "unit_amount": 150000 }
  ]
}
Response
{
  "id": "inv_9d02…",
  "object": "invoice",
  "number": "2026-0042",
  "status": "open",
  "collection_method": "send_invoice",
  "subtotal": 300000,
  "discount": 0,
  "total": 300000,
  "due": 1788208563,
  "hosted_url": "https://ronda.am/invoice/ivt_…",
  "created": 1786998563
}
GET/v1/invoicesscope: payments:read

List invoices, filterable by status and customer

GET/v1/invoices/:idscope: payments:read

Retrieve an invoice with its line items

POST/v1/invoices/:id/voidscope: payments:write

Void an invoice. It keeps its number — the sequence stays gapless — and stops being payable.

Coupons

A reusable discount rule. Attaching one to a subscription creates a discount with its own clock, so archiving the coupon never re-prices someone already on it. Discounts do not stack.

POST/v1/couponsscope: plans:write

Create a coupon — a percentage or a fixed amount, never both. A fixed amount_off needs its currency and applies only to prices in it.

Request
{
  "code": "SUMMER25",
  "percent_off": 25,
  "duration": "repeating",
  "duration_in_months": 3,
  "max_redemptions": 100
}
Response
{
  "id": "cpn_74b1…",
  "object": "coupon",
  "code": "SUMMER25",
  "percent_off": 25,
  "duration": "repeating",
  "duration_in_months": 3,
  "max_redemptions": 100,
  "times_redeemed": 0,
  "valid": true,
  "created": 1786998563
}
GET/v1/couponsscope: plans:read

List coupons

GET/v1/coupons/:idscope: plans:read

Retrieve a coupon by id or by code

DELETE/v1/coupons/:idscope: plans:write

Archive a coupon

POST/v1/subscriptions/:id/discountscope: subscriptions:write

Apply a coupon to a subscription. It takes effect from the next renewal.

Request
{ "coupon": "SUMMER25" }
DELETE/v1/subscriptions/:id/discountscope: subscriptions:write

Remove the discount; full price from the next renewal

Webhooks, events, disputes and balance

Everything else you need to reconcile.

POST/v1/webhook_endpointsscope: webhooks:write

Register an endpoint. The signing secret is returned once.

Request
{ "url": "https://yourapp.am/ronda/webhook", "enabled_events": ["payment.succeeded"] }
GET/v1/webhook_endpointsscope: webhooks:read

List endpoints

GET/v1/webhook_endpoints/:idscope: webhooks:read

Retrieve an endpoint

POST/v1/webhook_endpoints/:idscope: webhooks:write

Update url, description or enabled_events, or disable/enable it with { "disabled": true|false }. Enabling resets its failure count.

Request
{ "disabled": false }
POST/v1/webhook_endpoints/:id/testscope: webhooks:write

Send a signed webhook.test event now. The response is the delivery, with your server's HTTP status. One attempt, never retried, never counted toward auto-disable.

POST/v1/webhook_endpoints/:id/roll_secretscope: webhooks:write

Replace the signing secret. The new secret is in the response, once; the old one stops verifying immediately.

DELETE/v1/webhook_endpoints/:idscope: webhooks:write

Delete an endpoint

GET/v1/eventsscope: events:read

List events — supports ?type=

GET/v1/events/:idscope: events:read

Retrieve an event

GET/v1/disputesscope: payments:read

List chargebacks

GET/v1/disputes/:idscope: payments:read

Retrieve a chargeback: payment, amount, currency, reason, status, evidence_due_by and opened

GET/v1/balancescope: payments:read

Derived from the ledger. gross is everything collected; refunded and fees come off it. available (= settled) has reached your bank account; on_its_way is collected but not there yet, and pending is the part of on_its_way still clearing at the bank. Your bank pays you directly — Ronda never holds the money.

Response
{
  "object": "balance",
  "currency": "AMD",
  "available": 1180000,
  "on_its_way": 207900,
  "pending": 96000,
  "gross": 1420000,
  "refunded": 25000,
  "fees": 7100,
  "settled": 1180000,
  "mode": "live"
}