Skip to main content

4. API Reference

API Reference

All paths below are relative to the base URL (https://api.sirgiving.org for production, https://devapi.sirgiving.org for sandbox).

Auth column legend:

  • 🔐 HMAC — secret key + timestamp + signature
  • 🔑 HMAC (sk) — same, but pk_ keys are explicitly rejected (@RequiresSecretKey)
  • 🌐 Widget — publishable key only (pk_...), no signature
  • 🪪 JWT — partner-portal JWT (issued by /auth/login)

Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.


Partner Auth (portal)

These endpoints back the partner-user web portal, not server-to-server integration.

POST /v1/partner/auth/login

Email/password login for partner-portal users. Returns JWT pair + partner info + active publishable key.

GET /v1/partner/auth/me 🪪

Returns the authenticated partner user, their partner record, and active publishable key.


Partner Config 🌐

GET /v1/partner/config

Returns enabled features, rate limit, webhook URL, tip configuration. Used by widgets to bootstrap.

{
  "id": "...",
  "name": "Acme Inc",
  "slug": "acme",
  "enabledFeatures": ["DONATIONS", "ACTIONS"],
  "rateLimit": 5000,
  "isActive": true,
  "webhookUrl": "https://acme.com/webhooks/sir",
  "tipConfig": { "enabled": true, "defaultPercent": 10 }
}

Organizations 🌐

GET /v1/partner/organizations/:slug

Look up a nonprofit by slug (e.g. american-red-cross). Returns id, name, logo, EIN, status. Used by donation widgets.


Donations 🌐

POST /v1/partner/donations/create-link

Creates a payment link (Every.org or Stripe) attributed to your partner ID. Returns the URL plus a partnerDonationId to poll status with.

Request body:

{
  "nonprofitSlug": "american-red-cross",
  "amount": 1000,                 // cents
  "currency": "USD",
  "frequency": "once",            // "once" | "monthly"
  "provider": "every.org",        // or "stripe"
  "donorEmail": "donor@example.com",
  "firstName": "Jane",
  "lastName": "Doe",
  "tipAmount": 100,               // optional, Stripe path
  "tipPercent": 10,               // optional
  "referralCode": "REFER123",     // optional
  "organizationId": "..."         // optional, resolves from slug if omitted
}

Response:

{
  "donationLink": "https://every.org/donate/american-red-cross?...",
  "partnerDonationId": "550e8400-e29b-41d4-a716-446655440000",
  "estimatedSirReward": 70
}

GET /v1/partner/donations/status/:partnerDonationId

Poll-able status: PENDING | PROCESSING | COMPLETED | FAILED plus tokensDistributed.


Actions 🔐 / 🔑

The core write API. An action is a unit of value (a donation made, hours volunteered, an advocacy email sent) that distributes SIR tokens to one or more stakeholders per the configured campaign.

POST /v1/partner/actions/submit 🔑

Submit a single action. Idempotent on idempotencyKey. Returns synchronously after token distribution.

Body fields:

Field Required Notes
idempotencyKey yes ≤255 chars, unique per partner
actionType yes E.g. DONATION, VOLUNTEER, ADVOCACY
amount yes Numeric value (dollars/hours/points). >= 0
currency no Default USD
stakeholders[] yes See below
campaignId no Defaults to partner's default campaign
tokenPoolId no Defaults to partner's default pool
autoCreateUsers no Default true. Auto-creates partner users by userEmail
metadata no Free-form JSON

Stakeholder shape:

{
  "stakeholderTypeCode": "DONOR",
  "partnerUserId": "user_123",      // your external ID
  "userEmail": "user@example.com",  // for auto-create
  "userFirstName": "Jane",
  "userLastName": "Doe",
  "organizationId": "12-3456789",   // EIN, for ORG stakeholders
  "amount": 100,                    // override; usually derived from campaign split
  "index": 0,                       // for multiple of same type
  "metadata": {}
}

Status codes:

  • 200 — completed (response body has actionId, status, tokensDistributed, transactionIds[])
  • 400 — validation error
  • 402 — insufficient pool balance
  • 409 — duplicate idempotency key (returns the original cached result with 200-equivalent body)
  • 429 — rate limited

GET /v1/partner/actions/:id 🔐

Action details by ID. 404 if not yours.

POST /v1/partner/actions/:actionId/reverse 🔑

Trigger full or partial reversal. Requires actions:reverse scope on the API key.

{
  "reversalPercentage": 100,        // 0–100; 100 = full
  "reason": "Refunded by customer service",
  "refundIdempotencyKey": "rev_donation_2024_..."
}

Response includes tokensClawedBack, debtsCreated (when stakeholder balance is insufficient), and a per-stakeholder breakdown.

GET /v1/partner/actions 🔐

List with page, limit (max 100), status, actionType, fromDate, toDate.

POST /v1/partner/actions/bulk 🔑

Submit up to 100 actions for sequential synchronous processing. Response:

{
  "successful": 8,
  "failed": 2,
  "results": [
    { "idempotencyKey": "k1", "status": "COMPLETED", "actionId": "..." },
    { "idempotencyKey": "k2", "status": "FAILED", "error": "..." }
  ]
}

GET /v1/partner/actions/bulk/:jobId 🔐

Deprecated. Returns 410 Gone. Bulk is synchronous now — read the POST response.


Users 🔐

Users are keyed by your externalUserId. We mirror them with a SIR account/wallet.

GET /v1/partner/users 🔐

Paginate (page, limit, search).

POST /v1/partner/users 🔐

Create. Idempotent on externalUserId — returns existing user if already created.

{
  "externalUserId": "user_123",
  "email": "user@example.com",
  "firstName": "Jane",
  "lastName": "Doe",
  "metadata": {}
}

Response includes partnerUserId, accountId, walletCreated, upgradeEligible.

GET /v1/partner/users/:externalId 🔐

PATCH /v1/partner/users/:externalId 🔐 — update email/name/metadata

GET /v1/partner/users/:externalId/balance 🔐

Returns totalBalance, availableBalance, restrictedBalance, and balanceByRestriction[] (with availableAt for vested chunks).

GET /v1/partner/users/:externalId/transactions 🔐

The user's action history scoped to your partner. Supports page, limit, status, actionType, fromDate, toDate.


Token Pools 🔐

GET /v1/partner/token-pools 🔐

Lists allocated pools. Filter by status.

GET /v1/partner/token-pools/:id/balance 🔐

Pool balance breakdown: allocated, available, reserved, distributed, percent available.

POST /v1/partner/token-pools/request 🔐

Submit a request for additional allocation (admin-reviewed).

{ "amount": 100000, "reason": "Q3 campaign launch" }

GET /v1/partner/token-pools/requests 🔐

List your past allocation requests.


Campaigns 🔐

GET /v1/partner/campaigns 🔐 — list with status filter

GET /v1/partner/campaigns/active 🔐 — currently-running campaign for widget banners

GET /v1/partner/campaigns/:id 🔐

GET /v1/partner/campaigns/:id/analytics 🔐 — full analytics (stakeholder + tier breakdown, daily trends)

POST /v1/partner/campaigns 🔐 — create from template

{
  "tokenPoolId": "...",
  "name": "Holiday 2X",
  "description": "Double rewards for Q4",
  "templateId": "REWARD_MULTIPLIER",
  "actionTypes": ["DONATION"],
  "distribution": {
    "baseMultiplier": 2.0,
    "stakeholderSplits": [
      { "stakeholderTypeId": "DONOR", "percentage": 70, "isRequired": true, "splitMode": "EQUAL" },
      { "stakeholderTypeId": "ORGANIZATION", "percentage": 30, "isRequired": true, "splitMode": "EQUAL" }
    ],
    "tiers": [
      { "minAmount": 0, "maxAmount": 50, "multiplier": 1.0 },
      { "minAmount": 50, "maxAmount": null, "multiplier": 2.0 }
    ]
  },
  "limits": { "maxDistributionPerAction": 1000, "dailyDistributionLimit": 50000, "totalBudget": 1000000 },
  "schedule": { "startDate": "2024-11-01T00:00:00Z", "endDate": "2024-12-31T23:59:59Z" }
}

PATCH /v1/partner/campaigns/:id 🔐

Only DRAFT and ACTIVE are mutable. Completed campaigns return 400 CAMPAIGN_NOT_ACTIVE.


Transactions 🔐

A transaction is the double-entry ledger record produced by an action's token distribution.

GET /v1/partner/transactions 🔐

Filter by status, actionId, tokenPoolId, fromDate, toDate.

GET /v1/partner/transactions/:id 🔐

Returns full ledger detail: per-stakeholder entries[] with debit/credit/restrictionType, totalDebits, totalCredits, isBalanced, failureReason.


Webhooks 🔐 / 🔑

See the Webhooks page for delivery format and verification.

POST /v1/partner/webhooks 🔑 — register

{
  "url": "https://your-app.example.com/webhooks/sir",
  "description": "prod webhook",
  "eventTypes": ["action.completed", "action.failed", "transaction.completed"],
  "receiveAllEvents": false,
  "headers": { "X-My-Tenant": "prod" }
}

Response includes the signing secret — store it; you'll need it to verify deliveries.

GET /v1/partner/webhooks 🔐 — list

DELETE /v1/partner/webhooks/:id 🔑 — unregister

GET /v1/partner/webhooks/:id/deliveries 🔐

Filter by status (PENDING/DELIVERED/FAILED/RETRYING), eventType, paginate with limit/offset.

POST /v1/partner/webhooks/:id/deliveries/:deliveryId/retry 🔐

Manually re-queue a failed delivery for immediate redispatch.

POST /v1/partner/webhooks/:id/test 🔐

Sends a partner.status_changed ping with { type: 'ping', message, webhookId, timestamp } body.


Dashboard 🔐

Read-only analytics for partner-side dashboards.

Endpoint What it returns
GET /v1/partner/dashboard/summary Pool utilization, activity counts, user totals
GET /v1/partner/dashboard/trends?days=30 Daily series: actions, tokens, unique users, success rate (max 365d)
GET /v1/partner/dashboard/top-earners?limit=10 Top users by tokens earned (max 100)
GET /v1/partner/dashboard/webhooks/health Last-24h webhook success rate, avg response time, recent failures
GET /v1/partner/dashboard/api-usage?days=7 Daily request counts, rate-limited counts, error counts, top endpoints (max 90d)
GET /v1/partner/dashboard/recent-activity Last 10 actions, newest first
GET /v1/partner/dashboard/integration-health Webhook + token pool + action pipeline status
GET /v1/partner/dashboard/campaigns/:id/analytics Campaign performance breakdown

Error envelope

All errors return:

{
  "error": "ERROR_CODE",
  "message": "Human readable message",
  "requestId": "req_abc123",
  "retryAfter": 60          // only on 429
}

Common codes: INVALID_API_KEY, INVALID_SIGNATURE, TIMESTAMP_EXPIRED, PARTNER_NOT_ACTIVE, PARTNER_SUSPENDED, RATE_LIMIT_EXCEEDED, INVALID_REQUEST, INSUFFICIENT_SCOPE, USER_NOT_FOUND, ACTION_NOT_FOUND, CAMPAIGN_NOT_FOUND, POOL_NOT_FOUND, TRANSACTION_NOT_FOUND, WEBHOOK_NOT_FOUND, CAMPAIGN_NOT_ACTIVE, ENDPOINT_GONE, INTERNAL_ERROR.