Skip to main content
Version: v1

Endpoints

Seven endpoints under /api/v1/ingest. One is unsigned (pairing), one is signed by the single-use upload URL rather than HMAC (upload), and the rest are HMAC-signed per Authentication & HMAC.

EndpointMethodAuthIdempotency
/pairPOSTNone (TLS only)Pair code is single-use
/initPOSTHMAC
/upload/{sync_id}PUTSingle-use URLOverwrites by key
/finalizePOSTHMACIdempotency-Key (required for full snapshots)
/eventsPOSTHMACbatch_id + optional Idempotency-Key
/statusGETHMACAlways safe
/unpairPOSTHMACIdempotent

Full sync — three steps

A snapshot is inituploadfinalize.

POST /init

Opens a sync session and returns an upload target. sync_type is "full" (the snapshot path) or "partial"; expected_counts is a hint for dashboard progress, not validation.

Request
{ "sync_type": "full", "expected_counts": { "product": 245, "category": 18, "page": 32 } }
Response
{
"sync_id": "a7b1c4e2-19f0-4d6f-87aa-c00ffee12345",
"upload_url": "https://api.monoverse.tech/api/v1/ingest/upload/a7b1c4e2-...",
"object_key": "ingest/03d7d7e6-.../a7b1c4e2-....ndjson.gz",
"format": "ndjson_gzip",
"expires_at": "2026-05-16T11:00:00Z",
"max_size_bytes": 209715200
}

If a snapshot is already uploading or dispatching, init returns 409 conflict_full_sync_in_progress.

PUT /upload/{sync_id}

Stream the NDJSON body (one entity per line), optionally Content-Encoding: gzip, to the returned upload_url. No HMAC here — the URL is single-use and ingest-scoped (in production it's an S3 signed PUT). Max body is max_size_bytes (200 MB); over that is 413. Success is 204 No Content.

POST /finalize

Closes the session and schedules processing. Idempotency-Key is required for full-snapshot finalize — a retry with the same key returns the cached response and does no extra work.

Request → Response
{ "sync_id": "a7b1c4e2-..." }
// →
{ "sync_id": "a7b1c4e2-...", "status": "dispatching", "dispatch_eta_seconds": 60 }

After finalize the backend parses each NDJSON line, validates it, and writes the entities and their projections. Per-line failures are dead-lettered, not fatal.

Incremental — POST /events

Small signed batches of upserts and deletes. 1–500 operations per batch; body cap 5 MB. batch_id is your dedup id; replaying a seen batch_id returns idempotent_replay: true with processed: 0.

Request
{
"batch_id": "8e3a1b9c-...",
"site_clock": "2026-05-16T10:15:33Z",
"events": [
{ "type": "upsert", "entity_kind": "product", "external_id": "wc:product:iphone-15-pro",
"payload": { "name": "iPhone 15 Pro", "price_amount": 52999, "stock_status": "instock" } },
{ "type": "delete", "entity_kind": "product", "external_id": "wc:product:obsolete",
"deleted_at": "2026-05-16T10:15:30Z" }
]
}
Response
{
"processed": 1,
"errors": [ { "index": 1, "entity_kind": "product", "external_id": "wc:product:obsolete", "error": "..." } ],
"idempotent_replay": false
}

Each operation's payload follows the same per-kind shape as a snapshot line — see Entities.

GET /status

Read-only heartbeat. Returns the connection status, the last sync session summary, and per-kind entity counts. The producer may also send X-VoiceBot-Site-Url, X-VoiceBot-WP-Version, X-VoiceBot-WC-Version, X-VoiceBot-PHP-Version so the dashboard reflects the live site. Throttle to at most once every ~5 minutes.

POST /unpair

Disconnects the provider connection and invalidates the secret. Idempotent — a second call returns affected: 0. Run it before re-pairing a different tenant.

{ "ok": true, "affected": 1 }

Rate limits

ScopeLimit
/pair10 / min per IP
init + upload + finalize (combined)6 / min per tenant
/events60 / min per tenant
Full snapshots4 / hour per tenant

A 429 returns Retry-After. Note that signed 429s are not safe to blind-retry — see Errors.

Next

  • Entities — what goes in each NDJSON line and /events payload.