DocsAPI reference

API reference

Log an event

2 min readReviewed July 2026

POST /api/log appends one event to a collection.

Endpoint#

POST https://api.graphjson.com/api/log

Request body#

Field Type Required Description
api_key string Yes Workspace API key
collection string Recommended Destination collection; defaults to all_events when omitted
timestamp integer No Event time in Unix seconds; defaults to current server time
json string Yes Serialized JSON object, at most 10,000 characters
no_flatten boolean No Preserve nesting when true; default is false

Always send an explicit collection and timestamp in production. Defaults are useful for experimentation but can hide routing or time mistakes.

Node.js#

export async function logGraphJSON(event, timestamp = Date.now()) {
  const response = await fetch("https://api.graphjson.com/api/log", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      api_key: process.env.GRAPHJSON_API_KEY,
      collection: "product_events",
      timestamp: Math.floor(timestamp / 1000),
      json: JSON.stringify(event)
    })
  });

  const body = await response.text();

  if (!response.ok) {
    throw new Error(`GraphJSON ${response.status}: ${body}`);
  }

  return body ? JSON.parse(body) : null;
}

Python#

import json
import os
import time
import requests

response = requests.post(
    "https://api.graphjson.com/api/log",
    json={
        "api_key": os.environ["GRAPHJSON_API_KEY"],
        "collection": "product_events",
        "timestamp": int(time.time()),
        "json": json.dumps({
            "event": "checkout_completed",
            "user_id": "usr_42",
            "amount": 4900,
            "currency": "usd",
        }),
    },
    timeout=10,
)

response.raise_for_status()

curl#

curl --request POST \
  --url https://api.graphjson.com/api/log \
  --header 'Content-Type: application/json' \
  --data '{
    "api_key": "your_api_key",
    "collection": "product_events",
    "timestamp": 1785081600,
    "json": "{\"event\":\"checkout_completed\",\"user_id\":\"usr_42\",\"amount\":4900,\"currency\":\"usd\"}"
  }'

Serialization#

The entire HTTP body is JSON, and json is a serialized JSON object inside it:

body: JSON.stringify({
  api_key,
  collection,
  timestamp,
  json: JSON.stringify(event)
})

Sending json: event instead of json: JSON.stringify(event) returns a validation error.

The serialized payload must begin with {; top-level arrays are not accepted as one event.

Timestamps#

Use a whole Unix-second value:

Math.floor(Date.now() / 1000)

Millisecond and fractional timestamps are rejected. See Time and time zones.

Flattening#

By default, nested fields become dot-separated keys:

{"context":{"page":{"path":"/pricing"}}}

becomes:

{"context.page.path":"/pricing"}

Set no_flatten: true only when you intentionally want to preserve nested JSON and plan to query it with SQL. Flattened data is easier to use in the visualizer.

Success and failure#

Treat a 2xx status as accepted. Do not depend on undocumented fields in the success body.

Handle:

  • 400 by fixing the request; do not retry unchanged
  • 429 with exponential backoff and jitter
  • 500 or network errors with bounded retries when duplicate delivery is acceptable

The logging endpoint does not accept an idempotency key. If exact-once accounting matters, send a stable event ID and deduplicate in SQL.

Limits#

  • 10,000 serialized characters per event
  • 40 ingestion requests per second per workspace
  • one event per request

Use bulk logging to reduce request count.

Need a hand?

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

Contact support