Skip to main content

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

text
https://recurcite.com/api/v1

All 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:

bash
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 prefixEnvironmentUsage
rc_live_*ProductionEvents are stored and used for real dispute responses
rc_test_*TestEvents 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

ScopeLimitWindow
Per organization1,000 requests60 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

FieldTypeRequiredDescription
event_idstringNoUnique event ID for idempotency. Auto-generated if omitted.
typestringYesEvent type. One of: terms.accepted, user.login, product.used, cancellation.requested, cancellation.confirmed, support.ticket.created, support.ticket.resolved, transaction.completed
occurred_atISO 8601NoWhen the event happened. Defaults to current time if omitted.
payloadobjectYesEvent-specific data. See Event Reference for required fields per type.
stripe_refsobjectNoStripe object IDs for matching events to disputes. Always include stripe_customer_id.
email_sha256stringNoSHA-256 hex digest of the customer's email.

Example

bash
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

201Created
json
{
  "status": "accepted",
  "event_id": "terms-accepted-a1b2c3d4"
}
200Deduplicated
json
{
  "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.

bash
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

200OK
json
{
  "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>:

bash
# 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.

FieldFormatDescription
stripe_customer_idcus_*Primary matching key. Always include this.
stripe_subscription_idsub_*Links to a specific subscription
stripe_payment_intent_idpi_*Links to a specific payment
stripe_invoice_idin_*Links to a specific invoice

Error responses

All error responses include an error field with a human-readable message:

400Bad Request
json
{
  "error": "Invalid JSON body"
}
401Unauthorized
json
{
  "error": "Invalid or revoked API key"
}
422Validation Error
json
{
  "error": "Missing required field: payload.version"
}
429Rate Limited
json
{
  "error": "Rate limit exceeded"
}

The 429 response includes a Retry-After header with the number of seconds to wait before retrying.

CodeMeaningWhat to do
201Event acceptedSuccess — event stored
200Event deduplicated (single) or batch completedSafe to ignore — event was already recorded, or check batch results
400Invalid JSON or bad batch formatFix the request body
401Invalid or missing API keyCheck your X-API-Key header
422Payload validation failedCheck required fields for the event type
429Rate limitedBack off per Retry-After header
500Server errorRetry 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 OK with "deduplicated"

This makes it safe to retry failed requests without creating duplicate evidence. For deterministic IDs in the SDK, use generateEventId(type, uniqueKey):

typescript
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