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)
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)
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)
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)
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
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 field | semantic_events field | Notes |
|---|---|---|
| event_type | event_name | Normalized (lowercased, non-[a-z0-9_] → "_", capped 64 chars). Original kept in dims._source_event_name if changed. |
| event_properties + user_properties (prefixed user.*) | dims + measures | Merged, then one-level flattened. Numeric → measures (≤20 keys, revenue/total/price/quantity first); rest → dims (≤40 keys). |
| device_id | install_id | Falls back to user_id if device_id absent. At least one of the two is required — an event with neither is dropped. |
| user_id | external_id | |
| os_name + os_version | dims.os | Combined as "name version". |
| device_model (or device_family) | dims.device_model | |
| app_version | dims.app_version | |
| time (epoch ms) | occurred_at | Clamped to year-2000→now+1day; falls back to receive time if out of range or missing. |
| insert_id | event_id (dedup key) | Falls back to a hash of device_id/user_id + event_type + time when absent. |
| session_id | session_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:
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).