Currency

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 timestamppagination 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-Key is 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 same checkoutSessionId returns the original row with created: false. A retry that reuses externalRef under a different checkoutSessionId violates the (accountId, externalSource, externalRef) unique index and currently surfaces as 500 INTERNAL rather than a clean replay — keep checkoutSessionId stable.
  • POST /abandoned-cart/reminders — idempotent on (accountId, externalSource, externalRef) when you send both externalSource and externalRef; the service checks that tuple before inserting and returns the existing row with created: 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.
CurrencyRupiah is paid by transfer or QRIS; US dollars settle through PayPal.