Developers

Usage API

Last updated

One endpoint for login and usage events. A few minutes of work, and the strongest answer to “I never used it”.

1.Quickstart

When a customer disputes a charge, a bank wants to know they used what they paid for. Your product is the only place that knows. Send us a line each time a customer logs in or does something that matters, and we put the right ones in front of the bank: dates, IP addresses and devices.

  1. In Zevron, open Settings, Connections, Your product (usage API) and choose Create key. The key starts zv_live_ and is shown once.
  2. Check the key. This sends nothing:
curl https://zevron.ai/api/v1/events \
  -H "Authorization: Bearer $ZEVRON_KEY"
{
  "ok": true,
  "merchant": "Your store",
  "maxEventsPerRequest": 500,
  "eventsLast30d": 0,
  "lastEventAt": null
}
  1. Send your first event. The Connections page shows it straight away.
curl -X POST https://zevron.ai/api/v1/events \
  -H "Authorization: Bearer $ZEVRON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":"evt_1","email":"[email protected]","event":"login","at":"2026-10-01T14:03:00Z","ip":"203.0.113.7"}'

We only read an event when a dispute opens for that customer, and only the ones from the 60 days before it. Nothing is sold or shared, and a customer’s events are removed when they ask us to erase their data.

2.Keys and authentication

Send the key as Authorization: Bearer zv_live_…. Segment’s webhook destination sends it as the HTTP Basic username with an empty password, and that works too. A missing, wrong or stopped key gets 401.

We keep only a fingerprint of each key, so we cannot show it again. You can hold up to five keys, for example one per environment. Add one from Connections without touching the others.

To rotate a key, choose Rotate on its row. The new key works at once and the old one for 24 hours, so you can deploy without a gap, then it stops. Stop ends a key immediately, and anything still using it gets a 401.

3.Send events

POST /api/v1/events with a JSON body. Each event needs an email or a user_id, and an event name.

FieldTypeNotes
eventstring, requiredYour name for it: login, file_exported, lesson_completed. Names that look like a login are counted as logins. Up to 100 characters.
emailstringHow we find the customer: use the address on their card receipt. Required unless you send user_id.
user_idstringYour own ID. Works alone for your records, but cannot be matched to a dispute without an email.
atISO 8601 timeWhen it happened. Leave it out and it is the time we received it. Not in the future, not more than 800 days old.
ipstringA valid IPv4 or IPv6 address.
device_idstringAny stable device or browser ID, up to 200 characters.
idstringYour ID for this event. Sending it again is ignored. See retries below.
propertiesobjectAnything else, up to 8 KB. Set properties.platform to the tool the event came from.
curl -X POST https://zevron.ai/api/v1/events \
  -H "Authorization: Bearer $ZEVRON_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "evt_8812",
    "email": "[email protected]",
    "event": "login",
    "at": "2026-10-01T14:03:00Z",
    "ip": "203.0.113.7",
    "device_id": "d_91",
    "properties": { "plan": "pro", "country": "US" }
  }'
await fetch("https://zevron.ai/api/v1/events", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ZEVRON_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ id: "evt_8812", email: "[email protected]", event: "login", at: new Date().toISOString(), ip: "203.0.113.7" }),
});
import os, requests

requests.post(
    "https://zevron.ai/api/v1/events",
    headers={"Authorization": f"Bearer {os.environ['ZEVRON_KEY']}"},
    json={"id": "evt_8812", "email": "[email protected]", "event": "login", "at": "2026-10-01T14:03:00Z", "ip": "203.0.113.7"},
    timeout=10,
)

4.Batches

Send an array, or { "events": [...] }, with up to 500 events and under 1 MB. Each event is checked on its own: the good ones are stored, and the response says what happened to the rest.

curl -X POST https://zevron.ai/api/v1/events \
  -H "Authorization: Bearer $ZEVRON_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"id":"a1","email":"[email protected]","event":"login"},{"id":"b1","email":"not-an-email","event":"login"}]'
HTTP/1.1 202 Accepted

{
  "accepted": 1,
  "duplicates": 0,
  "rejected": [{ "index": 1, "error": "email: Invalid email address" }]
}

accepted were stored, duplicates were already there, and rejected lists the position (from 0) and the reason for each event we refused. A batch with a refused event still stores the rest; fix and resend only the ones listed.

5.Retries and idempotency

A network error does not tell you whether we got the request. Retrying is safe, two ways.

  • An id on each event. We store an event with a given id once per merchant, however often it arrives, now or next year. The second time it counts as a duplicate, not an error.
  • An Idempotency-Key header on the request. For events without an id: the same key and the same batch stores nothing twice. We match on the key and each event’s position in the batch, so send a new key for each different batch, for example nightly-2026-10-01.
curl -X POST https://zevron.ai/api/v1/events \
  -H "Authorization: Bearer $ZEVRON_KEY" \
  -H "Idempotency-Key: nightly-2026-10-01" \
  -H "Content-Type: application/json" \
  -d '[{"email":"[email protected]","event":"login"},{"email":"[email protected]","event":"login"}]'

A replay returns 202 with "accepted": 0 and a duplicates count, plus an Idempotent-Replay: true header. Retry 429 and 5xx with the same ids or key, and back off. Do not retry a 400 unchanged: it will be refused again.

6.Responses and errors

StatusMeaningWhat to do
202At least one event is stored, or was already stored. Check rejected for any we refused.Nothing. Resend only the rejected ones, fixed.
400Nothing was stored and something was refused: the body is not JSON, or every event failed a check. The reason is in rejected or error.Fix the request. Retrying it unchanged fails the same way.
401The key is missing, wrong or stopped. Body: { "error": "Invalid or missing API key" }.Check the key in Connections. A rotated-out key stops 24 hours after rotation.
413The body is over 1 MB, or there are more than 500 events.Split it into smaller requests.
429More than 600 requests a minute on one key. The Retry-After header says how many seconds to wait.Wait, then retry the same request. Batching events keeps you far under the limit.
5xxSomething failed on our side.Retry with the same ids or Idempotency-Key, with a growing delay.

Every error body is JSON with an error message you can log.

7.Limits

LimitValue
Events per request500
Request size1 MB
Requests per key600 a minute
properties per event8 KB
Age of an eventNot in the future, not more than 800 days old
Keys per account5 working at once
CSV upload5 MB and 20,000 rows, 30 uploads an hour

9.Segment, Zapier and files

Already on Segment? Add a webhook destination that points at https://zevron.ai/api/v1/events, with your key as the HTTP Basic username and an empty password. Track, identify, page and screen calls are accepted as they are, and Segment’s messageId prevents duplicates.

Zapier can post to the same address with a Webhooks step. Set properties.platform to the tool the event came from.

No developer? Upload a CSV under Settings, Connections, or post it: columns email (or user_id), event, at, and optionally ip, device_id and id. Any other column is kept as a property. The same file twice adds nothing.

curl -X POST https://zevron.ai/api/v1/events/import \
  -H "Authorization: Bearer $ZEVRON_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @events.csv

We do not connect to your database or warehouse. That would mean holding a credential into your production data, so a file you export and hand over is the safer way in.

10.What happens next

When a customer disputes, their events are quoted in the response as a login timeline. Your Connections page shows when we last received an event and how many. Questions? Write to the team from your dashboard, or see how we protect data.

Usage API · Zevron