Developer

REST API Reference

Kaduno Pullpush HTTP API. Base URL: http://localhost:4264 (dev) or https://api.pullpush.ai (prod).


Authentication

Most endpoints require a Bearer token in the Authorization header:

Authorization: Bearer <token>
Token type Scope Used for
Tenant API key (pp_...) Scoped to one tenant Event ingest, webhooks
MCP API key (MCP_API_KEY) Admin / all tenants MCP HTTP endpoint

Health

GET /health

Basic liveness check.

Response 200:

{ "ok": true, "service": "kaduno-pullpush-api" }

GET /health/ready

Readiness check — verifies Postgres and Redis connectivity.

Response 200 (all healthy):

{
  "ok": true,
  "checks": { "postgres": true, "redis": true },
  "service": "kaduno-pullpush-api"
}

Response 503 (dependency down):

{
  "ok": false,
  "checks": { "postgres": true, "redis": false },
  "service": "kaduno-pullpush-api"
}

Event Ingest

POST /api/v1/tenants/:tenantId/events

Ingest an event for a specific tenant. The event is deduplicated, written to the outbox, and queued for processing.

Auth: Tenant API key (Bearer)

Path params:

Param Type Description
tenantId string Tenant UUID

Body (JSON):

{
  "type": "order.created",
  "externalId": "ORD-12345",
  "occurredAt": "2026-06-16T10:00:00Z",
  "payload": {
    "orderId": "12345",
    "total": 599.00,
    "currency": "NOK"
  }
}
Field Type Required Description
type string Yes Event type (e.g. order.created, product.updated)
externalId string Yes Unique ID from the source system
occurredAt ISO 8601 No When the event happened (defaults to now)
payload object No Event data (defaults to {})

Response 200:

{
  "ok": true,
  "eventId": "clxyz...",
  "requestId": "uuid"
}

Errors:

Code Error Reason
401 missing_api_key No Authorization header
401 invalid_tenant_or_key Tenant not found or key mismatch
400 invalid_body Request body failed validation

Deduplication: Events are deduplicated on SHA256(tenantId + ":ingest:" + externalId). Sending the same event twice returns the existing eventId without creating a duplicate.


Webhooks

POST /api/v1/webhooks/:tenantId/:connectionId

Receive webhooks from external systems. The connector verifies the signature, parses events, and queues them for processing.

Auth: Signature verification (HMAC) — no Bearer token needed.

Path params:

Param Type Description
tenantId string Tenant UUID
connectionId string Source connection UUID

Headers: The connector determines which header to check:

  • X-Magento-Signature (Magento)
  • X-Signature (generic)
  • X-Hub-Signature (GitHub-style)

Body: Raw webhook payload (connector-specific format).

Response 202:

{
  "ok": true,
  "eventIds": ["clxyz...", "clabc..."],
  "requestId": "uuid"
}

Errors:

Code Error Reason
404 connection_not_found Connection doesn't exist or isn't active
400 connector_has_no_webhook Connector type doesn't support webhooks
401 invalid_signature HMAC verification failed

API Key Rotation

POST /api/v1/tenants/:tenantId/keys/rotate

Rotate the tenant's API key. Revokes all existing keys and issues a new one.

Auth: Current tenant API key (Bearer)

Response 200:

{
  "ok": true,
  "apiKey": "pp_abc123...",
  "prefix": "pp_abc12",
  "requestId": "uuid"
}

Important: Store the new key immediately — it cannot be retrieved again.


Flow Preview

GET /api/v1/tenants/:tenantId/flows/:flowId/preview

Zero-write sync preview: snapshot the flow's source + destination in canonical space and diff them. Useful before activating/cutting over a flow. Heavy (live snapshots) — on-demand only.

Auth: Tenant API key (Bearer) or MCP_API_KEY (trusted internal callers, e.g. the admin app).

Response 200: a SyncPreviewResult:

{
  "summary": { "total": 5333, "equal": 103, "changed": 84, "new": 5068, "missing": 78 },
  "items": [{ "key": "BC-1001", "source": {...}, "destination": {...}, "diff": "equal|changed|missing_in_dest|missing_in_source" }],
  "validation": { "source": [], "destination": [] }
}

Errors: 401 missing_api_key / invalid_tenant_or_key, 404 flow_not_found, 500 preview_failed.


OAuth2 Authorization Code Flow

Pullpush supports OAuth2 authorization_code grants for dynamic connectors whose definition declares auth.type: "oauth2". The flow stores a PKCE-protected state, redirects to the provider, exchanges the code for tokens, and encrypts them on the connection.

POST /api/v1/oauth/authorize

Initiate an OAuth2 flow. Returns the URL to redirect the user (or browser) to.

Auth: Tenant API key (Bearer) or MCP_API_KEY

Body (JSON):

{
  "tenantId": "clxyz...",
  "connectorType": "hubspot",
  "connectionId": "clxyz...",
  "scopes": "crm.objects.contacts.read",
  "redirectUri": "https://app.pullpush.ai/api/v1/oauth/callback"
}
Field Type Required Description
tenantId string Yes Tenant UUID
connectorType string Yes Slug of an active OAuth2 connector definition
connectionId string No Existing connection to update (omit to create new)
scopes string No Override scopes from the definition
redirectUri string No Override callback URL (default: {API_BASE_URL}/api/v1/oauth/callback)

Response 200:

{
  "ok": true,
  "redirectUrl": "https://provider.example.com/oauth/authorize?...",
  "state": "hex-random-state"
}

Errors:

Code Error Reason
401 missing_api_key No Authorization header
401 invalid_key Tenant/key mismatch
400 connector_not_oauth2 No active OAuth2 definition for the given type
400 missing_oauth_config Definition missing authorize_url or client_id

GET /api/v1/oauth/callback

OAuth2 callback. The provider redirects here after user consent. Exchanges the authorization code for tokens (with PKCE if enabled), stores encrypted credentials on the connection, and redirects the user to the admin UI.

Auth: None (validated by matching the state parameter).

Query params:

Param Type Description
code string Authorization code from the provider
state string State parameter (matched to OAuthState record)
error string Provider error code (if the user denied consent)
error_description string Human-readable error

Success: 302 redirect to {ADMIN_URL}/connections?oauth=success&connector={type}

Errors:

Code Error Reason
400 oauth_provider_error Provider returned an error (user denied / config issue)
400 missing_code_or_state Missing code or state query param
400 invalid_state No matching OAuthState record
400 expired_state OAuth flow timed out (10 min TTL)
502 token_exchange_failed Token endpoint returned an error

Other tenant routes

  • POST /api/v1/events — generic event ingest used by k-shop K-Connect. Auth via x-api-key header (tenant key). Body adds connectionId to the ingest shape; returns 202 { eventId }.
  • POST /api/v1/tenants/:tenantId/po-desk/* — PO Operations Desk (Linnworks PO → draft → review → accept → stock + Flowretail receivement). Authed like the other tenant routes.

MCP HTTP Endpoint

POST /mcp

JSON-RPC 2.0 endpoint for MCP tool calls. See MCP Tools Reference for available tools.

Auth: Bearer token (MCP_API_KEY for admin, or tenant API key for scoped access)

GET /api/mcp/info

Returns available MCP tool metadata (names, descriptions, schemas).

Response 200:

{
  "tools": [
    {
      "name": "pullpush.tenant.list",
      "description": "List all tenants (admin) or the caller tenant.",
      "inputSchema": { ... }
    }
  ]
}

Common patterns

Request ID tracing

Every request gets a unique requestId (from X-Request-Id header or auto-generated UUID). It's included in all responses and logs for correlation.

Rate limiting

The API enforces a global rate limit of 120 requests per minute per IP. Returns 429 Too Many Requests when exceeded.

Error format

All errors follow a consistent shape:

{
  "error": "error_code",
  "details": { ... }
}