POST /api/bulk-log appends multiple events to one collection in a single request.
Endpoint#
POST https://api.graphjson.com/api/bulk-log
Request body#
| Field | Type | Required | Description |
|---|---|---|---|
api_key |
string | Yes | Workspace API key |
collection |
string | Recommended | Destination collection |
timestamps |
integer[] | Yes | Unix-second timestamps |
jsons |
string[] | Yes | Serialized JSON objects |
no_flatten |
boolean | No | Preserve nested JSON when true |
timestamps[i] belongs to jsons[i]. The arrays must have the same length.
Example#
const events = [
{
timestamp: 1785081600,
body: {
event: "order_completed",
order_id: "ord_1",
amount: 4900
}
},
{
timestamp: 1785081612,
body: {
event: "order_completed",
order_id: "ord_2",
amount: 7900
}
}
];
const response = await fetch("https://api.graphjson.com/api/bulk-log", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
api_key: process.env.GRAPHJSON_API_KEY,
collection: "orders",
timestamps: events.map((event) => event.timestamp),
jsons: events.map((event) => JSON.stringify(event.body))
})
});
const body = await response.text();
if (!response.ok) {
throw new Error(`GraphJSON ${response.status}: ${body}`);
}
Limits#
One bulk request can contain:
- at most 50 events
- at most 10,000 serialized characters per event
- integer Unix-second timestamps only
The ingestion guardrail is 40 requests per second per workspace. A full batch can therefore carry substantially more events than single-event requests while using fewer connections.
Chunk a backfill#
function chunks(items, size) {
const result = [];
for (let index = 0; index < items.length; index += size) {
result.push(items.slice(index, index + size));
}
return result;
}
for (const batch of chunks(events, 50)) {
await sendGraphJSONBatch(batch);
}
Send batches sequentially or with conservative, bounded concurrency. Unbounded Promise.all calls can exceed the rate guardrail and make recovery harder.
Failure behavior#
GraphJSON validates the batch before forwarding it for ingestion. A malformed event causes the request to fail; fix the batch rather than assuming that every other row succeeded.
Recommended worker behavior:
- keep the source batch until a
2xxresponse - do not retry an unchanged
400 - retry
429, network failures, and5xxwith exponential backoff and jitter - cap retry attempts and move exhausted batches to a dead-letter queue
- include a stable
event_idwhen later deduplication may be necessary
The endpoint does not accept an idempotency key and does not promise exactly-once delivery.
Preserve event time#
For backfills, put the original event time in timestamps. Do not stamp an entire batch with the import time.
Validate a small batch in Samples before starting a large import, then reconcile counts and date ranges against the source.
For a queue-backed sender with retry and deduplication behavior, follow Build a reliable event delivery pipeline. For a historical backfill, start with the migration workflow.