Menu

Get started

Authentication

API keys, scopes and who may grant them, rep restriction, IP allowlists, rotation, expiry and leaked keys.

Every request carries an API key. A key belongs to one workspace, acts as the person who created it, and can do only what its scopes allow and what that person's role still allows.

Sending the key

Either header works. Use one, not both:

bash
curl https://api.dmhero.ai/v1/me -H "Authorization: Bearer $DMHERO_API_KEY"
curl https://api.dmhero.ai/v1/me -H "X-API-Key: $DMHERO_API_KEY"

A key looks like dmh_live_ followed by a 12 character prefix, an underscore and a 40 character secret. The prefix is not secret: it is shown in the app and in logs so you can tell keys apart. The secret is shown once, when the key is created; DM Hero stores only a hash of the whole key.

A missing, malformed, unknown, revoked or expired key, and a request from outside the key's IP allowlist, all get the same answer: 401 API_KEY_INVALID_401. The key's request log in the app tells its owner which one it was.

Server side only

Keep keys on servers, in environment variables or a secret manager. The API sends no CORS headers, so a browser page cannot call it with a key; that is deliberate, because a key in browser JavaScript is readable by anyone who opens the page.

Scopes

A key holds a list of scopes, and every operation names the scopes it needs (all of them). A missing scope answers 403 API_SCOPE_MISSING_403. Aliases: campaigns:enroll holds enrollments:write.

ScopeAllowsWho may grant itOperations
leads:read (Clay compatible)Read leads, tags, folders, custom fields, suppressions and jobs.Any memberleads.list, leads.get, leads.lookup, tags.list, folders.list, custom_fields.list, suppressions.list, leads.exports.create, jobs.list, jobs.get, jobs.result
leads:write (Clay compatible)Create, upsert and update leads; tags, folders, custom fields, suppressions; imports and additive bulk actions.Any memberleads.upsert, leads.update, leads.tags.add, leads.tags.remove, folders.create, folders.update, folders.delete, custom_fields.create, custom_fields.update, suppressions.add, suppressions.remove, campaign_leads.upsert_enroll, leads.imports.create, leads.bulk.preview, leads.bulk.confirm, jobs.cancel, discovery.candidates.promote
campaigns:read (Clay compatible)Read campaigns, their steps, stats and enrollments.Any membercampaigns.list, campaigns.get, campaigns.stats, enrollments.list, enrollments.get, leads.enrollments.list
campaigns:enroll (Clay compatible)The original enrollment scope of the Clay integration. Holds enrollments:write.Any member
leads:deleteDelete leads and custom fields, bulk delete and bulk status changes.Owner, adminleads.delete, custom_fields.delete
templates:readRead templates, their versions and stats; validate content.Any membertemplates.list, templates.get, templates.versions.list, templates.stats, templates.validate
templates:writeCreate, update and delete templates; spintax proposals.Any membertemplates.create, templates.update, templates.delete, templates.spintax.propose
campaigns:writeCreate, update, clone and delete campaigns; replace their steps.Any membercampaigns.create, campaigns.update, campaigns.steps.replace, campaigns.clone, campaigns.delete
campaigns:controlStart, pause, resume and archive campaigns.Any membercampaigns.start, campaigns.pause, campaigns.resume, campaigns.archive
enrollments:writeEnroll leads; pause, resume and remove one enrollment.Any membercampaign_leads.upsert_enroll, enrollments.create, enrollments.pause, enrollments.resume, enrollments.remove
reps:readRead reps, their health and their AI Setter mode.Any memberreps.list, reps.get, reps.setter_mode.get, reps.excluded.list
reps:writeSwitch the AI Setter mode; edit excluded handles.Owner, adminreps.setter_mode.set, reps.excluded.add, reps.excluded.remove
conversations:readRead threads as metadata: participant, status, folder, stage, reply intent, needs a human, reply window.Owner onlyconversations.list, conversations.get
analytics:readRead analytics.Any memberanalytics.overview, analytics.funnel, analytics.ab_comparison, analytics.setter
webhooks:manageManage webhook endpoints and deliveries.Owner, adminwebhooks.endpoints.list, webhooks.endpoints.create, webhooks.endpoints.get, webhooks.endpoints.update, webhooks.endpoints.delete, webhooks.endpoints.rotate_secret, webhooks.endpoints.test, webhooks.deliveries.list, webhooks.deliveries.redeliver, webhooks.events.list
discovery:readRead the token balance, modes, quotes, runs and candidates.Any memberdiscovery.balance, discovery.modes, discovery.icp_profiles.list, discovery.quote, discovery.runs.list, discovery.runs.get, discovery.candidates.list, discovery.candidates.promote
discovery:spendSpend discovery tokens: resolve handles, save recipes, start and cancel runs, promote candidates.Owner, admindiscovery.handles.resolve, discovery.recipes.create, discovery.runs.create, discovery.runs.cancel, discovery.runs.preview_decision
setter:readRead the AI Setter's configuration, versions, analytics and its pending drafts with their text. Never a lead's messages.Any membersetter.variants.list, setter.variants.get, setter.variants.versions.list, setter.knowledge.list, setter.persona.get, setter.booking.preview, setter.connections.get, conversations.drafts.list
setter:writeConfigure the AI Setter, hand threads to it or take them back, approve or dismiss its drafts. Approving sends the Setter's text.Owner, adminsetter.variants.create, setter.variants.update, setter.variants.publish, setter.variants.start_test, setter.variants.promote, setter.variants.archive, setter.knowledge.create, setter.knowledge.update, setter.knowledge.delete, setter.persona.set, setter.test, conversations.setter.hand, conversations.setter.resume, conversations.drafts.approve, conversations.drafts.dismiss
conversations:stateStar, unstar, archive, unarchive and mark threads read. Sends nothing.Owner onlyconversations.star, conversations.unstar, conversations.archive, conversations.unarchive, conversations.mark_read

Who may grant a scope is checked twice: when the key is created, and again on every request against the creator's current role. Demote the creator and the key loses the scopes the new role cannot grant, within seconds, without anyone touching the key.

Note: conversations:read and conversations:state can only be granted by the organization owner: such a key reaches every thread of its reps.

Developer keys and Clay compatible keys

Keys created before the developer API, and keys created with the Clay compatible preset, can call only the five original endpoints (GET /v1/me, GET /v1/campaigns, POST /v1/leads, GET /v1/leads/lookup, POST /v1/campaign-leads). Anything else answers 403 API_KEY_SURFACE_403. Create a developer key for the rest of the API.

Restricting a key to reps

A key can be limited to some of the workspace's reps (connected Instagram accounts). Such a key sees and acts on only their conversations, enrollments, setter modes, rep analytics and webhook endpoints filtered to them; another rep's resource answers 404 as if it did not exist. Workspace data (leads, templates, campaigns, tags, folders, custom fields, discovery) is shared by every key of the workspace.

IP allowlist

Where your organization has it available, a key can carry up to 20 IPv4 or IPv6 ranges in CIDR form. A request from any other address answers the same 401 as a wrong key, so whoever holds a stolen key learns nothing, and the owner's request log shows "IP not on the allowlist".

Expiry and rotation

  • A key expires after 30, 90, 365 days, or never; you choose when you create it. The app warns two weeks ahead.
  • Rotate to replace a key without downtime: the new key copies the old one's name, scopes, reps and IP ranges, and the old key keeps working for a grace period of 1 hour, 24 hours, 7 days (24 hours by default). Deploy the new key, then let the old one lapse.
  • Only the key's creator or the owner may rotate it, and whoever rotates becomes the new key's creator, so the role checks above run against them.
  • An Idempotency-Key used with the old key still deduplicates with the new one.
  • A workspace holds at most 10 active keys.

Revoking

Revoke a key in the app and it stops working everywhere within about five seconds. A member may revoke the keys they created; an owner or an admin may revoke any key of the workspace. A revoked key's queued jobs fail with JOB_KEY_REVOKED at their next step.

Revoking a key also switches off the webhook endpoints it created through the API, together with the ones created by the keys it was rotated from, and cancels their queued deliveries. They are not deleted: they show is_active: false with disabled_reason: "key_revoked", and an owner can switch them back on in the app, or another live key with webhooks:manage through the API (the endpoint then counts as created by that key). Rotating a key leaves them on, and so does revoking the old key once its replacement exists, since that only ends the grace period early. A key found leaked switches them off even then.

Leaked keys

Warning: If a key was ever exposed (pushed to a repository, pasted into a ticket, shipped in a client), revoke it in the app yourself at once, create a new key and remove the old one from your history. DM Hero is registering as a GitHub secret scanning partner. Once that is live, a dmh_live_ key pushed to a public GitHub repository is reported to us and revoked automatically: we cancel the jobs and discovery runs it started, switch off the webhook endpoints it created and mark it in the key list as "Revoked: found public on GitHub" with the link to where it was found. Until then, nothing revokes a leaked key for you.
Authentication | DM Hero Developers