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

  1. Open Settings → API keys.
  2. Give it a name you'll recognize later, e.g. "Keitaro" or "reporting script" (that's the only field the form takes).
  3. 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.
API keys panel with create key dialogAPI keys panel with create key dialog

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/events answers 403 or 401, 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:

  • clickId is 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.
  • type is 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, land and first_dm have no producer inside Leadgram and exist only because you send them, while bot_start, channel_join, join_request, miniapp_launch, purchase and the two subscription_* types already have a producer inside Leadgram - send those yourself and you get a second event beside the automatic one. bot_start is the one exception, and it is conditional: your report collapses onto the existing start only when it carries the Telegram user id in payload (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_username or payload.bot_id if you send one, otherwise the bot behind the clickId. A click from a plain redirect campaign, or one whose bot has been deleted, names no bot, so a report that also omits bot_username/bot_id is 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 different event_id, which no deduplication can collapse and which teaches your campaign optimizer that one person converted twice. See Events and conversions.
  • payout is 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.
  • nonce is optional, 1 to 128 characters. It's the idempotency key: the event id is derived from clickId + 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 different nonce. Instead of the field you can send an Idempotency-Key header, which is picked up as a fallback source for the same value.
  • payload is an optional free-form object, but three keys inside it are read specifically. telegram_user_id is the one billing and per-user postback dedupe key on. bot_username and bot_id apply to bot_start alone 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 answers 403 instead 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 nonce on 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 clickId returns 403, 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.