DocsAPI reference

API reference

Bulk log events

2 min readReviewed July 2026

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:

  1. keep the source batch until a 2xx response
  2. do not retry an unchanged 400
  3. retry 429, network failures, and 5xx with exponential backoff and jitter
  4. cap retry attempts and move exhausted batches to a dead-letter queue
  5. include a stable event_id when 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.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support