Amplitude-Compatible Acceptor

An Amplitude HTTP V2-compatible endpoint. Change one config value — the SDK's server URL — in your existing Amplitude instrumentation and events also land in Drengr's analysis layer. No new tracking code.

5-minute quickstart

Get a publishable key at drengr.dev/pro (free) — it goes in the request body as api_key, exactly where Amplitude's own SDKs put it (not a header):

Node (@amplitude/analytics-node)

javascript
import { init, track } from '@amplitude/analytics-node';

init('drengr_pk_YOUR_KEY', {
  serverUrl: 'function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/amplitude-ingest',
});

track('Order Completed', { revenue: 42.5 }, { user_id: 'user_123' });

Python (amplitude-analytics)

python
from amplitude import Amplitude, BaseEvent, Config

client = Amplitude('drengr_pk_YOUR_KEY', Config(
    server_url='function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/amplitude-ingest',
))

client.track(BaseEvent(
    event_type='Order Completed',
    user_id='user_123',
    event_properties={'revenue': 42.5},
))

Browser (@amplitude/analytics-browser)

javascript
import * as amplitude from '@amplitude/analytics-browser';

amplitude.init('drengr_pk_YOUR_KEY', {
  serverUrl: 'function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/amplitude-ingest',
});

amplitude.track('Order Completed', { revenue: 42.5 });

Raw HTTP (no SDK)

bash
curl -X POST function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/amplitude-ingest/2/httpapi \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "drengr_pk_YOUR_KEY",
    "events": [{
      "event_type": "Order Completed",
      "user_id": "user_123",
      "device_id": "abc-123",
      "event_properties": { "revenue": 42.5 },
      "os_name": "iOS",
      "os_version": "17.4",
      "time": 1750000000000
    }]
  }'

Endpoint & auth

POST
function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/amplitude-ingest

The /2/httpapi suffix real Amplitude SDKs append is accepted too — both forms hit the same handler, since this function has exactly one route.

Auth is the api_keyfield in the JSON body — that's the Amplitude wire contract, not a header. It is your drengr_pk_… publishable key. A bad key returns { code: 400, error: "invalid api_key" } — Amplitude SDKs only retry on 5xx/429, so an invalid key deliberately does not come back as 401.

Event mapping

Source fieldsemantic_events fieldNotes
event_typeevent_nameNormalized (lowercased, non-[a-z0-9_] → "_", capped 64 chars). Original kept in dims._source_event_name if changed.
event_properties + user_properties (prefixed user.*)dims + measuresMerged, then one-level flattened. Numeric → measures (≤20 keys, revenue/total/price/quantity first); rest → dims (≤40 keys).
device_idinstall_idFalls back to user_id if device_id absent. At least one of the two is required — an event with neither is dropped.
user_idexternal_id
os_name + os_versiondims.osCombined as "name version".
device_model (or device_family)dims.device_model
app_versiondims.app_version
time (epoch ms)occurred_atClamped to year-2000→now+1day; falls back to receive time if out of range or missing.
insert_idevent_id (dedup key)Falls back to a hash of device_id/user_id + event_type + time when absent.
session_idsession_id
email/phone/name/address/password/token/ssn/card/cvv-like keys(dropped)Same sensitive-key filter as every other door.

Limits

  • 1MB request body, 2,000 events per request
  • 5,000,000 events/day per tenant (soft cap, fails open on a metering error)
  • Malformed payloads never 5xx (Amplitude SDKs retry hard on 5xx/429) — only infra failures do

Response

Amplitude's own success contract, even when 0 events were valid:

json
200  { "code": 200, "events_ingested": 1, "payload_size_bytes": 214, "server_upload_time": 1750000012345 }

400  { "code": 400, "error": "invalid api_key" }
400  { "code": 400, "error": "missing or invalid events array" }
413  { "code": 413, "error": "payload too large" }
429  { "code": 429, "error": "exceeded daily quota" }

Troubleshooting

Each event needs a non-empty event_type AND at least one of device_id / user_id — Amplitude requires one identifier per event, and Drengr enforces the same rule. Malformed events are silently skipped, not rejected (fail-open per event).