Skip to content

Ingest API Reference

The Ingest API is the client-safe, high-volume API for tracking events, identifying contacts, and rendering widgets. Every endpoint is designed for low-latency ingestion from browsers, mobile apps, and edge workers.


Base URLhttps://ingest.growthos.io/v1
AuthAuthorization: Bearer gos_wk_...
Content-Typeapplication/json

All requests require a valid Write Key. Pass it via the Authorization header (recommended) or as a writeKey query parameter.

POST /v1/track HTTP/1.1
Host: ingest.growthos.io
Authorization: Bearer gos_wk_your_write_key
Content-Type: application/json

Identify or update a contact. Creates the contact if it does not exist (upsert). Traits are shallow-merged with existing data — set a trait to null explicitly to delete it.

{
"user_id": "usr_123",
"traits": {
"email": "jane@acme.com",
"name": "Jane Doe",
"plan": "growth",
"company": "Acme Inc",
"signed_up_at": "2025-01-15T10:00:00Z"
},
"context": {
"ip": "auto",
"user_agent": "auto",
"locale": "en-US"
},
"timestamp": "2025-06-01T12:00:00Z"
}
FieldTypeRequiredDescription
user_idstringconditionalYour internal user identifier. Required if anonymous_id is not provided.
anonymous_idstringconditionalA UUID for anonymous visitors. Required if user_id is not provided.
traitsobjectnoKey-value pairs describing the contact. Shallow-merged with existing traits. Set a value to null to delete it.
contextobjectnoContextual metadata. See Context Object below.
timestampstringnoISO 8601 timestamp. Defaults to server receipt time if omitted.
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"request_id": "req_8x2m4k9p"
}

Track a custom event with optional properties.

{
"user_id": "usr_123",
"event": "feature_activated",
"properties": {
"feature": "referral_widget",
"plan": "growth",
"value": 49.00
},
"timestamp": "2025-06-01T12:05:00Z",
"message_id": "msg_abc123"
}
FieldTypeRequiredDescription
user_idstringconditionalRequired if anonymous_id is not provided.
anonymous_idstringconditionalRequired if user_id is not provided.
eventstringyesEvent name. Max 256 characters.
propertiesobjectnoArbitrary key-value pairs describing the event.
timestampstringnoISO 8601 timestamp. Defaults to server receipt time.
message_idstringnoClient-generated unique ID for idempotent delivery. Deduped within 24 hours.
contextobjectnoContextual metadata. See Context Object.
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"request_id": "req_3n5p7q2z"
}

Track a page view. Automatically enriched with context fields when using the JavaScript SDK.

{
"user_id": "usr_123",
"name": "Pricing Page",
"properties": {
"url": "https://acme.com/pricing",
"referrer": "https://google.com",
"title": "Pricing - Acme"
}
}
FieldTypeRequiredDescription
user_idstringconditionalRequired if anonymous_id is not provided.
anonymous_idstringconditionalRequired if user_id is not provided.
namestringnoA human-readable name for the page (e.g., “Pricing Page”).
propertiesobjectnoPage metadata — url, referrer, title, path, search, keywords.
contextobjectnoContextual metadata. See Context Object.
timestampstringnoISO 8601 timestamp. Defaults to server receipt time.
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"request_id": "req_6k8m2n4p"
}

Associate a contact with a company or organization. Use this to power account-level analytics and B2B features.

{
"user_id": "usr_123",
"group_id": "company_456",
"traits": {
"name": "Acme Inc",
"industry": "SaaS",
"plan": "scale",
"employee_count": 45
}
}
FieldTypeRequiredDescription
user_idstringconditionalRequired if anonymous_id is not provided.
anonymous_idstringconditionalRequired if user_id is not provided.
group_idstringyesYour identifier for the company or organization.
traitsobjectnoKey-value pairs describing the group. Shallow-merged with existing traits.
contextobjectnoContextual metadata. See Context Object.
timestampstringnoISO 8601 timestamp. Defaults to server receipt time.
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"request_id": "req_1a3b5c7d"
}

Send multiple calls in a single HTTP request. Reduces connection overhead and improves throughput for high-volume producers.

{
"batch": [
{
"type": "identify",
"user_id": "usr_123",
"traits": { "name": "Jane Doe" }
},
{
"type": "track",
"user_id": "usr_123",
"event": "login"
},
{
"type": "page",
"user_id": "usr_123",
"name": "Dashboard"
}
]
}
FieldTypeRequiredDescription
batcharrayyesArray of event objects. Each must include a type field.

Each item in the batch array follows the same schema as the corresponding individual endpoint, with an additional type field.

type valueCorresponding endpoint
identifyPOST /v1/identify
trackPOST /v1/track
pagePOST /v1/page
groupPOST /v1/group
ConstraintLimit
Max events per batch500
Max payload size500 KB

Each item is processed independently — partial success is possible. The response includes per-item status.

HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"request_id": "req_9z3n5p7q",
"items": [
{ "index": 0, "status": 202, "success": true },
{ "index": 1, "status": 202, "success": true },
{ "index": 2, "status": 422, "success": false, "error": "Missing required field: name" }
]
}

Merge two contact identities. Typically used to link an anonymous visitor to a known user after login or signup.

{
"previous_id": "anon_xyz789",
"user_id": "usr_123"
}
FieldTypeRequiredDescription
previous_idstringyesThe anonymous or old identifier to merge from.
user_idstringyesThe canonical user identifier to merge into.
  1. All events and traits from previous_id are transferred to user_id
  2. The previous_id becomes a permanent alias pointing to user_id
  3. Future events sent with previous_id are automatically attributed to user_id
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"request_id": "req_2b4d6f8h"
}

Retrieve active feature flags, experiment variants, and widget configurations for a specific contact. This is the only Ingest API endpoint that returns data.

GET /v1/decide?user_id=usr_123
ParameterTypeRequiredDescription
user_idstringconditionalRequired if anonymous_id is not provided.
anonymous_idstringconditionalRequired if user_id is not provided.
HTTP/1.1 200 OK
Content-Type: application/json
{
"feature_flags": {
"new_onboarding": true,
"dark_mode": false
},
"active_experiments": [
{
"key": "pricing_test",
"variant": "B"
}
],
"widgets": {
"referral": {
"enabled": true,
"config": {
"reward_type": "credit",
"reward_amount": 20,
"currency": "USD"
}
},
"nps": {
"enabled": true,
"delay_ms": 30000
}
}
}

Every Ingest API call accepts an optional context object for metadata enrichment. When using the JavaScript SDK, most fields are populated automatically.

FieldTypeAuto-populatedDescription
ipstringyesClient IP address. Set to "auto" to use the request IP.
user_agentstringyesBrowser or client user agent string. Set to "auto" for automatic detection.
localestringyesUser locale (e.g., en-US).
page.urlstringyesFull URL of the current page.
page.pathstringyesURL path (e.g., /pricing).
page.referrerstringyesReferring URL.
page.titlestringyesDocument title.
screen.widthintegeryesScreen width in pixels.
screen.heightintegeryesScreen height in pixels.
library.namestringyesSDK name (e.g., growthos-js).
library.versionstringyesSDK version (e.g., 1.4.2).
campaign.sourcestringnoUTM source parameter.
campaign.mediumstringnoUTM medium parameter.
campaign.namestringnoUTM campaign name.
campaign.termstringnoUTM term.
campaign.contentstringnoUTM content.

Events prefixed with $ are reserved by GrowthOS and have special handling in the platform. Do not use these prefixes for custom events.

Event NameDescriptionTriggered By
$page_viewPage view recordedSDK auto-track or /v1/page
$form_submittedA GrowthOS-managed form was submittedWeb Components
$referral_createdA referral link was generatedReferral widget
$referral_convertedA referred user completed signupReferral engine
$survey_respondedA user submitted a survey responseSurvey widget
$waitlist_joinedA user joined a waitlistWaitlist widget
$email_openedAn email was opened (pixel tracked)Email engine
$email_clickedA link in an email was clickedEmail engine
$email_bouncedAn email delivery bouncedEmail engine
$email_unsubscribedA user unsubscribed from emailsEmail engine
$experiment_viewedAn experiment variant was displayedA/B testing engine
$nps_submittedAn NPS score was submittedNPS widget

Include a message_id in any event payload to enable idempotent delivery. If GrowthOS receives two events with the same message_id within a 24-hour window, the duplicate is silently discarded.

{
"user_id": "usr_123",
"event": "purchase_completed",
"properties": { "order_id": "ord_789", "amount": 99.00 },
"message_id": "msg_unique_abc123"
}

How it works

The message_id is hashed and stored in a fast lookup table with a 24-hour TTL. Duplicates within that window return 202 Accepted but are not re-processed.

When to use it

Always include message_id when retrying failed requests or sending from at-least-once delivery systems (e.g., message queues). The SDK generates one automatically for every call.


All Ingest API write endpoints return 202 Accepted immediately after validating the payload structure. Events are placed on an internal event bus and processed asynchronously.

AspectGuarantee
Acknowledgment202 returned in under 50ms (p99)
Processing latencyEvents processed within 5 seconds (p95)
DurabilityEvents are persisted to the event bus before 202 is returned
OrderingPer-user ordering is preserved. Cross-user ordering is best-effort.
DeliveryAt-least-once. Use message_id for exactly-once semantics.

Even though successful calls return 202, the Ingest API validates payloads synchronously and returns errors immediately.

StatusCodeDescription
400bad_requestMalformed JSON or missing Content-Type header.
401unauthenticatedMissing or invalid Write Key.
413payload_too_largeBatch payload exceeds 500 KB.
422validation_errorValid JSON but fails schema validation (e.g., missing event field).
429rate_limitedToo many requests. Check the Retry-After header.
{
"error": {
"code": "validation_error",
"message": "Field 'event' is required for track calls.",
"details": [
{
"field": "event",
"reason": "required",
"message": "Every track call must include an event name."
}
]
},
"request_id": "req_4f6h8j0l"
}

POST /v1/identify

Upsert a contact with traits. Requires user_id or anonymous_id. Returns 202.

POST /v1/track

Record a custom event. Requires event name. Supports message_id for idempotency. Returns 202.

POST /v1/page

Log a page view with URL, referrer, and title metadata. Returns 202.

POST /v1/group

Associate a contact with a company. Requires group_id. Returns 202.

POST /v1/batch

Send up to 500 events in one request. Max 500 KB. Per-item status in response.

POST /v1/alias

Merge anonymous identity into known user. Irreversible. Returns 202.

GET /v1/decide

Fetch feature flags, experiments, and widget config. Synchronous 200 response.