Skip to content
JuicePay

JuicePay documentation

Create account

Documentation

Authentication

API keys, scopes, and request signing.

JuicePay authenticates with bearer API keys. Keys are environment-scoped and carry a set of scopes that bound what they can do.

Send a key#

bash
curl -s "$JUICEPAY_URL/v1/balances" \
  -H "Authorization: Bearer $JUICEPAY_KEY"

A key that is missing, malformed, revoked, or issued for the other environment returns 401 unauthenticated. A key that is valid but lacks the required scope returns 403 permission_denied.

Key formats#

PrefixEnvironmentExample
jp_test_Sandboxjp_test_9f2KxQ7mLp4Vn8Rt
jp_live_Productionjp_live_4mW8bZ2nHq6Yc1Pd

Prefixes exist so a leaked key is identifiable in a log scanner. Treat a production key in a public repository as compromised and rotate it immediately — revocation takes effect within seconds.

Scopes#

Grant the narrowest set that works. Scopes are space-separated and readable from the key's metadata.

ScopeGrants
balances:readRead balances and ledger entries
payment_intents:writeCreate and update inbound payments
payouts:writeCreate single payouts and batches
swaps:writeQuote and execute conversions
webhook_endpoints:writeRegister, update and replay endpoints
reports:readDownload settlement reports

A checkout service typically needs payment_intents:write and balances:read and nothing more. It should not hold payouts:write; if your checkout service is ever compromised, outbound payments should be impossible.

Read-only keys#

For dashboards, internal tooling, and anything a human holds, mint a read-only key:

bash
curl -s -X POST "$JUICEPAY_URL/v1/api_keys" \
  -H "Authorization: Bearer $JUICEPAY_KEY" \
  -d '{ "name": "analytics-readonly", "scopes": ["balances:read", "reports:read"], "read_only": true }'

Restricted keys by IP#

json
{
  "name": "ci-deploy",
  "scopes": ["payment_intents:write"],
  "allowed_ips": ["203.0.113.0/24", "198.51.100.7"]
}

Requests from outside the list are rejected before authentication is evaluated, and the attempt appears in your audit log.

Rotating keys#

Rotation is two-phase so you never have a window with no working key.

  1. Create a replacement key with the same scopes.
  2. Deploy it to every consumer and confirm traffic has moved by watching last_used_at.
  3. Revoke the old key.
bash
curl -s -X DELETE "$JUICEPAY_URL/v1/api_keys/key_old123" \
  -H "Authorization: Bearer $JUICEPAY_KEY"

Never authenticate from a browser or mobile client with a secret key. Use a payment intent and its client secret, which is scoped to a single payment and cannot be replayed elsewhere.

Request signing (optional)#

Accounts handling large volumes can require signed requests. Sign the timestamp and the raw body:

ts
import { createHmac } from "node:crypto";

const timestamp = Math.floor(Date.now() / 1000);
const payload = `${timestamp}.${rawBody}`;
const signature = createHmac("sha256", secret).update(payload).digest("hex");

await fetch(url, {
  method: "POST",
  headers: {
    "JuicePay-Timestamp": String(timestamp),
    "JuicePay-Signature": `v1=${signature}`,
  },
  body: rawBody,
});

Signed requests are rejected if the timestamp is more than five minutes old, which bounds the replay window.