WePeopleDevelopers
IntegrationsPricingDevelopersSign inGet started
OverviewGuideReferenceOpenAPI YAML

Guide · concepts and how-to

Guide

The Ingest API, read end-to-end.

Everything your team needs to wire one of your own services into WePeople: the model we use, the two endpoints you’ll call, the footguns we built guardrails around, and the bits we left sharp on purpose. Pair this with the API reference for the per-endpoint contract.

On this page

  • Concepts
  • Authentication
  • Identifying workers
  • Events
  • Snapshots
  • Idempotency
  • Rate limits & caps
  • Errors
  • Security

Concepts

The Ingest API is private and per-organization. You create one or more developer apps inside an organization; each app owns its own API keys, its own whitelist of event types, and its own synthetic integration connection. Events ingested by an app show up on the monitoring dashboard as custom:<app-slug>, side by side with every other connected integration.

There are two primitives you’ll send: events (discrete things that happened, timeline-worthy) and snapshots(point-in-time gauges, rendered as a card on each worker’s strip).

Authentication

Authenticate every request with a bearer token. Generate keys from Organization settings → Developer → New key. A key is shown once and prefixed with wp_live_; it’s stored as a bcrypt hash server-side, so lost keys can’t be recovered — revoke them and issue a new one.

Authorization headerhttp
Authorization: Bearer wp_live_9f3c3e4c1e9b4b2d9c1e3f4b2c3d4e5f

Smoke-test your key with GET /api/v1/ingest/ping. It echoes the organization id and the app’s slug so you can fail fast on deploy.

Identifying workers

Every event and snapshot belongs to a worker. The actor object accepts three identifiers; the server resolves them in order:

  1. workerId — existing WePeople worker.
  2. externalId— your system’s stable id, recommended. Unique per app.
  3. email — last-resort match against the org directory.

When nothing matches, WePeople auto-provisions a worker and links it to the app’s custom:<slug> connection — no pre-sync needed.

Events

Events are batched; send up to 500 per request. Each entry needs an eventType(must be in the app’s whitelist), a category, and an actor. The metadata object is a free-form ≤ 16 KB payload that shows up verbatim in the Events panel.

POST /api/v1/ingest/eventsjson
{
  "events": [
    {
      "eventType": "deploy.shipped",
      "category": "code",
      "timestamp": "2026-04-19T12:05:00Z",
      "actor": { "externalId": "eng-17" },
      "duration": 612,
      "metadata": {
        "service": "checkout-api",
        "sha": "3c4d8a1",
        "env": "production"
      }
    }
  ]
}

The response includes accepted, rejected, and a per-row breakdown. Partial success yields 207 — resend only the rejected indices instead of the whole batch.

Snapshots

A snapshot replaces the previous snapshot for the same (worker, snapshotType) pair. The user strip renders up to four metrics per app, in insertion order. Metrics may be plain numbers, short strings, or rich objects with a unit and label.

POST /api/v1/ingest/snapshotsjson
{
  "snapshotType": "oncall_queue",
  "actor": { "email": "alex@acme.com" },
  "metrics": {
    "active": 2,
    "waiting": { "value": 5, "unit": "count", "label": "queued" },
    "p95_ack": { "value": 3.4, "unit": "minutes", "label": "p95 ack" }
  }
}

Idempotency

Add an Idempotency-Key header on every write. The first response for a given key is cached for ten minutes and replayed verbatim on retry — safe to use from background queues and cron jobs with at-least-once delivery.

The TypeScript SDK generates one per call automatically. Override it if you want retries from your queue to dedupe:

installbash
npm install @wepeople/sdk
@wepeople/sdkts
await client.ingestEvents(events, {
  idempotencyKey: `${jobId}:${attempt}`,
});

Rate limits & caps

Three independent budgets apply to every request:

  • Per key: sliding window, defaults to 60 req/s (burst).
  • Per organization: 600 req/s aggregate across all keys, prevents a single noisy integration from starving the rest.
  • Monthly events: set per plan (maxEventsPerMonth). 402 comes back when you blow through it — upgrade to continue.

On 429, respect the Retry-After header. The SDK does this for you with exponential backoff and jitter.

Errors

Every error is JSON-shaped with a stable code, a human-readable message, and the requestId to cite in support.

Error payloadjson
{
  "error": {
    "code": "forbidden_event_type",
    "message": "eventType 'deploy.shipped' is not in this app's whitelist.",
    "requestId": "req_01HP3K5V4J3M9NVT8XZKQ9YQ2E",
    "docsUrl": "https://…/developers/reference#errors"
  }
}

The most useful codes to handle distinctly: invalid_key, plan_limit, rate_limited, forbidden_event_type, and invalid_body. Everything else is transient and retryable.

Security

Keys are bcrypt-hashed, shown once, and revocable with a click. All writes are over TLS; the API never follows redirects from your requests; metadata is stored as JSONB and is retrievable only within the owning organization. If you think a key leaked, revoke it immediately from the Developer tab — the revoke takes effect on the next auth lookup (≤ 60 seconds with the current cache).

WePeople Ingest API · v1

API referenceOpenAPI YAMLSDK (GitHub)npmPricingBack to site