Push data from your system
Build a reliable sync job — upserts, batches, retries, and deletes.
Use an Ingest only key (documents:write) for sync jobs. If the job also needs to read its documents back — to diff, say — add documents:read.
Upsert one document
curl https://api.synqed.dev/v1/documents \
-H "Authorization: Bearer $SYNQED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "zendesk",
"external_id": "ticket-48213",
"title": "Refund flow fails for annual plans",
"body": "Customer reports …",
"url": "https://acme.zendesk.com/agent/tickets/48213",
"metadata": { "priority": "high", "plan": "annual" }
}'sourceis your label for the system: lowercase letters, digits,_ . -, up to 64 characters. Use one label per system.- The response says whether the document was
createdorupdated, and returns itsid. The id is stable for a given(source, external_id), so storing it is optional — upserting again always reaches the same document. metadatais replaced as a whole on each upsert, never merged.
Batch
Send up to 100 documents per request to POST /v1/documents/batch:
const docs = rows.map((r) => ({
source: 'notion',
external_id: r.id,
title: r.title,
body: r.markdown,
url: r.url,
}));
for (let i = 0; i < docs.length; i += 100) {
await fetch('https://api.synqed.dev/v1/documents/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNQED_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `notion-sync-${runId}-${i}`,
},
body: JSON.stringify({ documents: docs.slice(i, i + 100) }),
});
}The whole batch is checked before anything is written — validation, visibility, and whether any document belongs to data the key can't access — so a rejected batch writes nothing. Each (source, external_id) may appear only once per batch. (A failure while writing, such as a timeout, can still leave earlier documents written; retrying the same batch is safe because upserts converge.)
Retries
Retry on 429 (honour Retry-After), 5xx and network errors with exponential backoff. Upserts are naturally safe to repeat. For other POSTs, send an Idempotency-Key so a retry never writes twice.
Updates and deletes
- Re-upsert to change a document — or
PATCH /v1/documents/{id}to change single fields. DELETE /v1/documents/{id}removes the document and its search index entries. Call it when the original is deleted so agents never cite stale content.
Visibility
Documents inherit the key's data access. To push finance documents that only the Finance group should see, create a key with Selected groups → Finance. A key with all-data access can instead set "visibility": { "groups": ["finance"] } per document.
Every key with documents:write in a workspace shares the same document space: two keys pushing the same (source, external_id) update the same document. Give each system its own source label.