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:
400by fixing the request; do not retry unchanged429with exponential backoff and jitter500or 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.