Developer Platform

APIs & Widgets

Embed live Spirit & Water metrics on any site, or integrate directly with our REST API and SSE stream. No authentication required for public read endpoints.

Quick start

The fastest way to show 238 Global data on your site is an iframe or our drop-in embed.js script. Both load pre-built widgets from /embed/* routes with minimal chrome.

Live preview — Metrics widget

Embed widgets

Three widget types are available: metrics (4 counters), feed (scrolling activity list), and globe (interactive 3-D globe with live report markers).

HTML — iframe
<iframe
  src="https://238global.com/embed/metrics"
  width="100%"
  height="120"
  frameborder="0"
  title="238 Global Metrics"
></iframe>
HTML — embed.js (vanilla JS)
<script src="https://238global.com/embed.js" data-base="https://238global.com"></script>

<div data-238="metrics" data-width="100%" data-height="120"></div>
<div data-238="feed" data-width="100%" data-height="320"></div>
<div data-238="globe" data-width="100%" data-height="400"></div>
React
function MetricsWidget() {
  return (
    <iframe
      src="/embed/metrics"
      width="100%"
      height="120"
      style={{ border: 0 }}
      title="238 Global Metrics"
    />
  );
}

Attributes

data-238
Widget type: metrics, feed, or globe
data-base
Base URL on the script tag (for cross-origin embeds)
data-src
Override iframe src entirely
data-width / data-height
Dimensions (defaults: 100% × 120 or 320)

REST API

All public read endpoints return JSON. Base path: /api/v1

GET/api/v1/metrics

Aggregate counters for Spirit infillings, water baptisms, combined events, and totals — including today's counts.

{
  "spirit": 14832,
  "water": 9451,
  "both": 7206,
  "total": 31489,
  "spiritToday": 127,
  "waterToday": 84,
  "bothToday": 63,
  "totalToday": 274,
  "countries": 42,
  "cities": 186,
  "updatedAt": "2026-07-09T18:00:00.000Z"
}
GET/api/v1/reports

Recent approved reports. Query params: limit (max 100), since (ISO datetime).

GET /api/v1/reports?limit=10&since=2026-07-01T00:00:00Z

{
  "reports": [
    {
      "id": "clx…",
      "city": "Lagos",
      "country": "Nigeria",
      "peopleCount": 23,
      "type": "spirit",
      "createdAt": "2026-07-09T17:45:00.000Z"
    }
  ]
}
GET/api/v1/streamSSE

Server-Sent Events stream. On connection and every 5 seconds, two named events are emitted:

  • metricsAggregate counters identical to GET /api/v1/metrics.
  • reportsObject { reports: [...] } — the 20 most recent approved reports.
  • errorEmitted only on server-side tick failure; { message: string }.
JavaScript — EventSource
const es = new EventSource('/api/v1/stream');

es.addEventListener('metrics', (e) => {
  const metrics = JSON.parse(e.data);
  console.log('Total:', metrics.total);
});

es.addEventListener('reports', (e) => {
  const { reports } = JSON.parse(e.data);
  console.log('Latest city:', reports[0]?.city);
});

es.addEventListener('error', (e) => {
  // native EventSource error (connection lost) — reconnects automatically
  // named 'error' events from server indicate a tick failure
});

Authentication

Public read endpoints work without any credentials. To access higher rate limits, include your API key as a Bearer token in the Authorization header.

Request with API key
GET /api/v1/metrics HTTP/1.1
Authorization: Bearer 238_<your-api-key>

GET /api/v1/reports?limit=100 HTTP/1.1
Authorization: Bearer 238_<your-api-key>

API keys are issued by the 238 Global team. Contact [email protected] to request a partner key.

Rate limits

Rate limits apply per IP for unauthenticated requests, or per API key for authenticated requests. Exceeding limits returns 429 Too Many Requests.

Public
60 / min
Unauthenticated reads (per IP)
Partner
1,000 / min
Bearer API key required
Enterprise
Unlimited
Contact us for access

Examples

Fetch metrics
const res = await fetch('https://238global.com/api/v1/metrics');
const { total, spiritToday } = await res.json();
document.getElementById('count').textContent = total.toLocaleString();
Poll reports every 30s
async function poll() {
  const res = await fetch('/api/v1/reports?limit=5');
  const { reports } = await res.json();
  renderFeed(reports);
}
setInterval(poll, 30_000);
poll();

Webhooks

Partner keys can subscribe to real-time event webhooks. When an event fires, 238 Global POSTs a signed JSON payload to your endpoint. Register subscriptions via the API.

Supported events

report.created
Fired when a report is published. Payload contains the full report object.
metrics.updated
Fired after each report approval with the updated aggregate metrics.
Register a webhook subscription
POST /api/v1/webhooks
Authorization: Bearer 238_<your-api-key>
Content-Type: application/json

{
  "url": "https://yourapp.example.com/webhooks/238",
  "events": ["report.created", "metrics.updated"]
}

// Response — save the secret, shown once
{
  "subscription": {
    "id": "sub_…",
    "url": "https://yourapp.example.com/webhooks/238",
    "events": ["report.created", "metrics.updated"],
    "secret": "abc123…",
    "createdAt": "2026-07-09T22:00:00.000Z"
  }
}
List subscriptions
GET /api/v1/webhooks
Authorization: Bearer 238_<your-api-key>
Delete a subscription
DELETE /api/v1/webhooks?id=sub_…
Authorization: Bearer 238_<your-api-key>

Verifying signatures

Every webhook request includes an X-238-Signature header. The value is sha256=<hex> computed as HMAC-SHA256 of the raw request body using your subscription secret.

Node.js — verify signature
import { createHmac, timingSafeEqual } from 'crypto';

function verifyWebhook(body: string, header: string, secret: string): boolean {
  const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
  return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

// Express / Next.js route handler
app.post('/webhooks/238', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-238-signature'] as string;
  if (!verifyWebhook(req.body.toString(), sig, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }
  const { event, data } = JSON.parse(req.body.toString());
  console.log('Received event:', event, data);
  res.sendStatus(200);
});

Webhook payload structure

{
  "event": "report.created",
  "timestamp": "2026-07-09T22:00:00.000Z",
  "data": {
    "id": "clx…",
    "city": "Lagos",
    "country": "Nigeria",
    "peopleCount": 23,
    "type": "spirit",
    "status": "APPROVED",
    "createdAt": "2026-07-09T22:00:00.000Z"
  }
}