API keys
API keys are your account's credential for the Leadgram API. Manage them from Settings: create a key, give it a name, and revoke it when you're done. Here's what a key can and cannot do, how to create one, how to send an event with it, and how to rotate or revoke it.
What API keys are for
An API key lets an external tool or script report conversions to Leadgram on your behalf, without sharing your login credentials or session cookie. It's built for server-to-server integrations: an affiliate network, an advertiser's CRM, or your own script fires an event at the moment of the deposit, instead of a human clicking around the dashboard. It isn't meant for browser use.
Create a key
- Open Settings → API keys.
- Give it a name you'll recognize later, e.g. "Keitaro" or "reporting script" (that's the only field the form takes).
- Click Create Key. The secret is shown exactly once, right after creation, in a copy box, so copy it now. Neither the keys table nor any endpoint will show the full value again; only the name and creation date persist.

What a key can and cannot do
A key is tied to your account and acts inside your organization, but it is not full dashboard access:
- It can send events to
POST /api/events. That is the only thing a key unlocks, write or read. - It cannot do anything else: create or change campaigns, flows, bots, postback rules, teammates, or billing. Destructive mutations are off the table entirely, so a leaked key cannot be used to tear your account down.
- It cannot read your data either. Clicks, reports, campaigns, subscribers, events, billing: every one of those endpoints, and every dashboard page, treats a key as no credential at all. A key-authenticated request anywhere but
POST /api/eventsanswers403or401, no matter what its owner is allowed to do in the browser.
There's no per-teammate permission split in the current UI: if several people or systems need integration access, mint a key per integration so you can tell them apart in the table and revoke exactly one without breaking the others.
Sending an event: POST /api/events
The key travels on the x-api-key header:
curl -X POST https://leadgram.org/api/events \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"clickId": "tz4k9m2p1xq7v0b3n8s6d5fg",
"type": "deposit",
"payout": 42.50,
"nonce": "crm-conversion-8813",
"payload": { "telegram_user_id": 123456789 }
}'
Body fields:
clickIdis required. The click id Leadgram issued, i.e. the value that reached you through the{click_id}macro in an offer URL, a campaign redirect, or a landing. A click belonging to another organization won't do.typeis required, one of:click,land,bot_start,channel_join,join_request,first_dm,miniapp_launch,lead,registration,deposit,ftd,purchase,subscription_renewed,subscription_cancelled,custom_1-custom_8. Accepting a type is not the same as producing it:click,landandfirst_dmhave no producer inside Leadgram and exist only because you send them, whilebot_start,channel_join,join_request,miniapp_launch,purchaseand the twosubscription_*types already have a producer inside Leadgram - send those yourself and you get a second event beside the automatic one.bot_startis the one exception, and it is conditional: your report collapses onto the existing start only when it carries the Telegram user id inpayload(missing it is a 422,bot_start_missing_telegram_user_id) and something in the request can name the bot the user started -payload.bot_usernameorpayload.bot_idif you send one, otherwise the bot behind theclickId. A click from a plain redirect campaign, or one whose bot has been deleted, names no bot, so a report that also omitsbot_username/bot_idis written as its own separate event. That does not cost you a second charge - Leadgram bills once per person per workspace, and a Telegram user id matches that person every time, so the duplicate is free - but it does put a second Lead into Meta under a differentevent_id, which no deduplication can collapse and which teaches your campaign optimizer that one person converted twice. See Events and conversions.payoutis optional. The payout for this conversion: a number between 0 and 1,000,000 (a string like"42.50"is accepted too, negatives are rejected). Omit it and the conversion inherits the Payout of the offer attached to the click.nonceis optional, 1 to 128 characters. It's the idempotency key: the event id is derived fromclickId+type+nonce, so resending the same request never writes a second row and never double-fires the charge, the postbacks, or the ad-platform conversions. The flip side is that two genuinely different conversions on the same click (a second deposit) must send a differentnonce. Instead of the field you can send anIdempotency-Keyheader, which is picked up as a fallback source for the same value.payloadis an optional free-form object, but three keys inside it are read specifically.telegram_user_idis the one billing and per-user postback dedupe key on.bot_usernameandbot_idapply tobot_startalone and say which bot the user actually started - the @name (with or without the@), or Leadgram's internal bot id. Both are optional and worth sending as soon as you run more than one bot: without either, the pairing falls back to the bot behind the click, which is a guess and is wrong whenever that click came from a campaign fronting a different bot. A name that is not one of your bots answers403instead of quietly falling back to the guess.
A successful call answers 201 with the created event row. 400 means the body failed validation, 403 means the click wasn't found or belongs to another organization, 401 means the key wasn't accepted. The endpoint allows 300 requests per minute per organization; beyond that you get 429 with a Retry-After header.
Rotate and revoke
There's no in-place "regenerate": a key's secret is fixed for its lifetime. To rotate one, create a new key first, swap it into your integration, confirm events still land, then revoke the old key from the table so nothing keeps running on it. Revoking is immediate: click Revoke on a key's row and it stops working right away, with no grace period, so do the swap before the revoke, not after.
Common pitfalls
- The secret is shown once, at creation. Close the dialog before copying it and it's gone for good; revoke the key and create a new one.
- A key writes conversions into your organization, so a leaked key means someone else's events in your analytics and your billing. It cannot read your data or touch your account, but that is still your money and your reports: don't paste it into a support chat, a public repo, or a shared script, and rotate anything that leaked.
- Forgetting
nonceon retries. Without it a resend collapses into one row, which is fine, but a second genuine conversion on the same click disappears exactly the same way. - A foreign or stale
clickIdreturns403, not a quiet success: nothing is recorded, and silence in the reports is what that looks like. - Revoking has no undo and no delay: any integration still using the old key starts failing immediately.
- Name your keys for what they're plugged into (e.g. "Keitaro prod") so you know which one to revoke when something breaks, instead of guessing from a bare creation date.