SDK & API
API Reference
The RecurCite Evidence API is a JSON REST API for ingesting evidence events. You can use any HTTP client — the Node.js SDK is a thin wrapper around these endpoints.
Base URL
https://recurcite.com/api/v1All endpoints accept and return JSON. Use Content-Type: application/json for all requests.
Authentication
Authenticate every request with your API key in the X-API-Key header:
curl -X POST https://recurcite.com/api/v1/events \
-H "Content-Type: application/json" \
-H "X-API-Key: rc_live_your_api_key_here" \
-d '{ ... }'| Key prefix | Environment | Usage |
|---|---|---|
rc_live_* | Production | Events are stored and used for real dispute responses |
rc_test_* | Test | Events are stored but marked as test data; not used in live submissions |
Get your API key
Go to Dashboard → Developers and click Create key. The key is displayed once — store it in your environment variables.
Rate limits
| Scope | Limit | Window |
|---|---|---|
| Per organization | 1,000 requests | 60 seconds (sliding window) |
When rate-limited, the API returns 429 Too Many Requests with a Retry-After header (seconds). The Node.js SDK automatically retries with exponential backoff.
POST /api/v1/events
Send a single evidence event. Returns 201 Created on success, or 200 OK if the event was deduplicated.
Request
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string | No | Unique event ID for idempotency. Auto-generated if omitted. |
type | string | Yes | Event type. One of: terms.accepted, user.login, product.used, cancellation.requested, cancellation.confirmed, support.ticket.created, support.ticket.resolved, transaction.completed |
occurred_at | ISO 8601 | No | When the event happened. Defaults to current time if omitted. |
payload | object | Yes | Event-specific data. See Event Reference for required fields per type. |
stripe_refs | object | No | Stripe object IDs for matching events to disputes. Always include stripe_customer_id. |
email_sha256 | string | No | SHA-256 hex digest of the customer's email. |
Example
curl -X POST https://recurcite.com/api/v1/events \
-H "Content-Type: application/json" \
-H "X-API-Key: rc_live_your_api_key" \
-d '{
"type": "terms.accepted",
"payload": {
"version": "2.0",
"accepted_at": "2025-06-15T10:30:00Z"
},
"stripe_refs": {
"stripe_customer_id": "cus_abc123"
}
}'Response
{
"status": "accepted",
"event_id": "terms-accepted-a1b2c3d4"
}{
"status": "deduplicated",
"event_id": "terms-accepted-a1b2c3d4"
}A 200 with "deduplicated" means an event with the same event_id already exists. This is safe — your data was already recorded.
POST /api/v1/events?batch=true
Send up to 100 events in a single request. Pass an array as the request body, or add ?batch=true to the URL.
curl -X POST "https://recurcite.com/api/v1/events?batch=true" \
-H "Content-Type: application/json" \
-H "X-API-Key: rc_live_your_api_key" \
-d '[
{
"type": "product.used",
"payload": {
"feature_key": "api_calls",
"count": 150,
"occurred_at": "2025-06-15T10:30:00Z"
},
"stripe_refs": { "stripe_customer_id": "cus_abc123" }
},
{
"type": "user.login",
"payload": {
"occurred_at": "2025-06-15T10:31:00Z",
"ip": "203.0.113.42"
},
"stripe_refs": { "stripe_customer_id": "cus_abc123" }
}
]'Batch response
{
"summary": {
"accepted": 2,
"deduplicated": 0,
"errors": 0,
"total": 2
},
"results": [
{ "event_id": "product-used-a1b2c3d4", "status": "accepted" },
{ "event_id": "user-login-e5f6g7h8", "status": "accepted" }
]
}Note
Each event in a batch is processed independently. Some events may succeed while others fail — always check the results array.
HMAC signing
Sign requests with HMAC-SHA256 for tamper-proof event delivery. The server stores verification status alongside each accepted event.
Include the signature in the X-Recurcite-Signature header with the format sha256=<hex>:
# Generate the signature
BODY='{"type":"terms.accepted","payload":{"version":"2.0","accepted_at":"2025-06-15T10:30:00Z"},"stripe_refs":{"stripe_customer_id":"cus_abc123"}}'
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$RECURCITE_SIGNING_SECRET" | awk '{print $2}')
curl -X POST https://recurcite.com/api/v1/events \
-H "Content-Type: application/json" \
-H "X-API-Key: $RECURCITE_API_KEY" \
-H "X-Recurcite-Signature: sha256=$SIG" \
-d "$BODY"Stripe references
Every event accepts an optional stripe_refs object to link evidence to Stripe objects. Include as many as possible — especially stripe_customer_id, which is the primary key for matching events to disputes.
| Field | Format | Description |
|---|---|---|
stripe_customer_id | cus_* | Primary matching key. Always include this. |
stripe_subscription_id | sub_* | Links to a specific subscription |
stripe_payment_intent_id | pi_* | Links to a specific payment |
stripe_invoice_id | in_* | Links to a specific invoice |
Error responses
All error responses include an error field with a human-readable message:
{
"error": "Invalid JSON body"
}{
"error": "Invalid or revoked API key"
}{
"error": "Missing required field: payload.version"
}{
"error": "Rate limit exceeded"
}The 429 response includes a Retry-After header with the number of seconds to wait before retrying.
| Code | Meaning | What to do |
|---|---|---|
201 | Event accepted | Success — event stored |
200 | Event deduplicated (single) or batch completed | Safe to ignore — event was already recorded, or check batch results |
400 | Invalid JSON or bad batch format | Fix the request body |
401 | Invalid or missing API key | Check your X-API-Key header |
422 | Payload validation failed | Check required fields for the event type |
429 | Rate limited | Back off per Retry-After header |
500 | Server error | Retry with exponential backoff; contact support if persistent |
Idempotency
Every event has an event_id. If you omit it, the server generates a unique one. If you provide your own, the server uses it for deduplication:
- First request with a given
event_id→201 Created - Subsequent requests with the same
event_id→200 OKwith"deduplicated"
This makes it safe to retry failed requests without creating duplicate evidence. For deterministic IDs in the SDK, use generateEventId(type, uniqueKey):
import { generateEventId } from "@recurcite/sdk";
await recurcite.track({
event_id: generateEventId("terms.accepted", userId),
type: "terms.accepted",
payload: { version: "2.0", accepted_at: new Date().toISOString() },
});Next steps
- Event Reference — required fields and examples for every event type
- SDK Installation — install the Node.js SDK for automatic signing, retries, and TypeScript types
- Security & Data — HMAC signing, encryption, and data handling