API reference
The Ripllo API is a REST API that uses JSON for both requests and responses. It's the same API our portal and SDKs use; if you want to integrate Ripllo directly without an SDK, this section covers everything you need.
Base URL
https://ripllo.com
All API routes live under /api/v1. There's no separate api. subdomain — the marketing site and the API share the apex.
For staging:
https://staging.ripllo.com
Staging keys are minted separately from production keys; they don't cross.
Authentication
Every request must be signed. The Ripllo API uses HMAC-SHA256 request signing with an access key ID + secret pair.
Two headers per request:
| Header | What it is |
|---|---|
Authorization |
Ripllo-HMAC-SHA256 keyId=<id>, scope=*, signature=<hex> |
X-Ripllo-Timestamp |
Current epoch seconds (must be within 300 seconds of server time) |
Optional headers:
| Header | When to use |
|---|---|
Idempotency-Key |
An opaque string folded into the string-to-sign when present. It does not make the server deduplicate — see Idempotency. |
X-Ripllo-On-Behalf-Of |
Only honoured for keys that hold the ripllo:platform:admin scope. Rescopes the call to a downstream merchant. |
The signing string is:
<METHOD>
<path> (without query string)
<timestamp>
<sha256-hex(body)>
[<idempotency-key>] (optional, if present)
See Authentication for the full signing recipe with worked examples.
The SDKs handle all this transparently. You only need to compute signatures manually with raw HTTP.
Response envelope
Every successful API response is wrapped in a uniform envelope — the same @forjio/sdk/http shape every Forjio product uses:
{
"data": { /* the response payload */ },
"error": null,
"meta": {
"requestId": "req_01H...",
"timestamp": "2026-05-12T10:42:00Z"
}
}
meta only ever carries requestId and timestamp — pagination is never in meta. Cursor-paginated list endpoints put the page state inside data, alongside the rows:
{
"data": { "items": [ /* … */ ], "total": 128, "nextCursor": "clx3k9v0000...", "hasMore": true },
"error": null,
"meta": { "requestId": "req_01H...", "timestamp": "2026-05-12T10:42:00Z" }
}
The key names vary by resource — discount codes use { items, total, nextCursor, hasMore }, contacts and the marketplace lists use { data, cursor, hasMore }. Either way, feed the cursor value back as the ?cursor= query param to get the next page. Check the per-resource page before writing a pagination loop.
Errors keep the envelope shape but set error:
{
"data": null,
"error": {
"code": "VALIDATION",
"message": "value must be a positive integer",
"details": { "field": "value" }
},
"meta": { "requestId": "...", "timestamp": "..." }
}
error has exactly three keys: code, message, and an optional details object. There is no top-level field. Schema failures emit the code VALIDATION (not VALIDATION_ERROR), with the zod message in message.
Resources
The API is organized by resource. Each maps to a section of the dashboard:
| Resource | Path prefix |
|---|---|
| Discount codes | /api/v1/discount-codes |
| Referrals | /api/v1/referrals |
| Abandoned cart | /api/v1/abandoned-cart |
| Pixels | /api/v1/pixels |
| Feeds | /api/v1/feeds |
| Blog | /api/v1/blog |
| Marketing campaigns | /api/v1/marketing-campaigns |
| Contacts | /api/v1/contacts |
| Contact lists | /api/v1/contact-lists |
| Audience segments | /api/v1/audience-segments |
| Funnels | /api/v1/funnels |
| Channels | /api/v1/channels |
| Integrations | /api/v1/integrations |
| Campaigns (creator marketplace) | /api/v1/campaigns |
| Programs (affiliate marketplace) | /api/v1/programs |
| Collaborations | /api/v1/collaborations |
| Inbox | /api/v1/inbox |
| Insights | /api/v1/insights |
| API keys | /api/v1/api-keys |
| Webhooks | /api/v1/webhooks |
| Audit log | /api/v1/audit-log |
| Billing | /api/v1/billing |
| Admin (partners) | /api/v1/admin |
Each resource follows a CRUD-like surface (GET list, GET :id, POST create, PATCH update, DELETE archive) with extras specific to that resource. See the SDK source for the exact method list per resource — the Node SDK is the most readable reference.
Idempotency
Idempotency-Keyis not a replay cache. The header is optional, and the server's only use for it is signing: when present it is appended to the string-to-sign as a trailing\n<key>line, so the value has to be identical on client and server or the signature fails. Ripllo does not store keys, does not deduplicate, and does not replay a cached response. Sending the same key twice runs the operation twice.
Retry safety comes from per-resource natural keys instead:
POST /discount-codes/redeem— idempotent on(accountId, checkoutSessionId). A retry with the samecheckoutSessionIdreturns the original row withcreated: false. A retry that reusesexternalRefunder a differentcheckoutSessionIdviolates the(accountId, externalSource, externalRef)unique index and currently surfaces as500 INTERNALrather than a clean replay — keepcheckoutSessionIdstable.POST /abandoned-cart/reminders— idempotent on(accountId, externalSource, externalRef)when you send bothexternalSourceandexternalRef; the service checks that tuple before inserting and returns the existing row withcreated: false. Omit either and every retry records a new reminder.
The Node SDK does auto-generate an Idempotency-Key for a handful of create-style methods (discountCodes.create, blog.create, loyalty.earn/redeem) and signs it correctly — but that only satisfies the signature; it buys no dedupe.
Check the resource page before assuming a retry is safe.
Rate limits
No rate limiting is enforced today. There is no limiter in the request chain, no X-RateLimit-* response headers, and the API does not return 429.
Plans do carry a rateLimit number (60 on Free, 600 on Starter, 2000 on Growth, 5000 on Scale — echoed back in the billing limits payload), but it is informational: nothing meters against it yet. Treat published throughput as a courtesy, not a contract, and back off on 5xx.
Partner billing (X-Ripllo-On-Behalf-Of)
If you're a platform (Storlaunch is the canonical example) calling Ripllo on behalf of one of your merchants, your key needs the ripllo:platform:admin scope, and you set the X-Ripllo-On-Behalf-Of header to the merchant's account ID (acc_<storlaunchAccountId>).
That scope cannot be self-served: POST /api/v1/api-keys only accepts read, write and admin, so partner-admin keys are provisioned out-of-band by Forjio as part of partner onboarding. Ordinary keys you mint yourself will never hold it. Ripllo rescopes the entire request to that merchant's workspace and stamps both the partner ID and the on-behalf-of ID in the audit log.
Ordinary merchant keys (no admin scope) cannot use this header — you get 403 FORBIDDEN_ONBEHALF. See Authentication for the full mechanics.
Next
- Authentication — the full HMAC signing recipe with worked examples.
- SDKs — per-language guides.
- Concepts — the data model the API exposes.