Reference
Create an API key
POST /v1/api-keys. Create a standard key or an agent key.
POST /v1/api-keys
Create a standard key or an agent key. An agent key has a label, allowed tools, a monthly budget, a per-order cap, an expiry and a webhook, and is created by a signed-in person through OAuth, never by a key. An agent key needs allowed_tools and per_order_max_credits and never takes admin. Unlimited keys on every plan; the secret is shown once.
| Property | Value |
|---|---|
| Endpoint | POST https://mydatahub.emailmovers.com/v1/api-keys |
| Operation id | createApiKey |
| Cost | Free (0 credits) |
| Access | A signed-in person only: an OAuth 2.1 token with the scope admin. API keys are refused. |
| Idempotency-Key | Optional. Makes a retry safe. |
| Tag | Keys |
Request
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
MyDataHub-Version | header | string | no | The dated API version, pinned per key when left out. Non-breaking changes ship as new dates; the path major changes only for breaking changes, with a 12 month deprecation window. |
Idempotency-Key | header | string | no | Makes the request safe to retry: the same key returns the stored response. Keys are kept for the life of the organisation. |
Body
| Member | Type | Required | Description |
|---|---|---|---|
kind | enum: standard, agent | yes | |
label | string | yes | |
mode | enum: live, test | no | live keys (mdh_live_) spend real credits; test keys (mdh_test_) return synthetic data and never charge. Default live. |
scopes | array of enum: account:read, counts:read, previews:read, audiences:read, audiences:write, quotes:read, orders:read, orders:write, and 8 more | yes | The key's scopes. An agent key never takes admin, and no key takes account:write, which only a signed-in owner uses. |
ip_allowlist | array of string | no | CIDR ranges the key may be used from; empty allows any address. |
expires_at | string (date-time) | no | RFC 3339 date and time, with Z or an offset such as +01:00. |
allowed_tools | array of enum: get_plans, get_account, list_filters, search_filter_values, expand_job_titles, resolve_location, compile_audience, count_audience, and 14 more | no | Agent keys only, and required there: the tools the key may call. |
allowed_connection_ids | array of string | no | Agent keys only: the connections push_to_crm may push to live. Default none. |
monthly_budget_credits | integer (0 or more) | no | Agent keys only. Default 1000 credits a month (to confirm). |
per_order_max_credits | integer (0 or more) | no | Agent keys only, and required there: the most one order may spend, no more than monthly_budget_credits. |
webhook_url | string (uri) | no | Agent keys only: the https endpoint its events go to. |
disclosure | enum: account, anonymous | no | Agent keys only. anonymous makes every count follow an anonymous visitor's rules, for a public assistant. Default account. |
licence_accepted_version | string | no | Agent keys only, and required for one that spends on its own (create_order or enrich_contact, with a monthly budget and a per-order cap above 0): the version of the Data Licence and Sharing Agreement the person making the key accepts, the account's agreements.dlsa.version. Every order and enrichment the key makes on its own carries that acceptance. A key with a budget or cap of 0 needs none: a person approves, and accepts, each of its spends. |
Example request
curl -X POST "https://mydatahub.emailmovers.com/v1/api-keys" \
-H "Authorization: Bearer $MYDATAHUB_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"kind": "agent",
"label": "SDR agent",
"scopes": [
"counts:read",
"quotes:read",
"orders:write",
"orders:read",
"records:read",
"pushes:write"
],
"allowed_tools": [
"count_audience",
"quote_order",
"create_order",
"fetch_order_records",
"push_to_crm"
],
"allowed_connection_ids": ["conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G"],
"monthly_budget_credits": 1000,
"per_order_max_credits": 500,
"expires_at": "2027-03-31T00:00:00Z",
"licence_accepted_version": "1.0"
}'Responses
201: The key and its secret.
Headers: RateLimit, RateLimit-Policy, Location.
| Member | Type | Always present | Description |
|---|---|---|---|
key | StandardApiKey or AgentKey | yes | A standard API key or an agent key. The secret is never shown again. |
secret | string | yes | The secret, shown once. Send it as Authorization: Bearer. |
Example response
{
"key": {
"id": "key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3",
"kind": "agent",
"label": "SDR agent",
"mode": "test",
"prefix": "mdh_test_Exam",
"scopes": [
"counts:read",
"quotes:read",
"orders:write",
"orders:read",
"records:read",
"pushes:write"
],
"ip_allowlist": [],
"created_at": "2026-09-20T09:00:00Z",
"created_by": "usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8",
"last_used_at": "2026-09-26T02:00:00Z",
"expires_at": "2027-03-31T00:00:00Z",
"revoked_at": null,
"allowed_tools": [
"count_audience",
"quote_order",
"create_order",
"fetch_order_records",
"push_to_crm"
],
"allowed_connection_ids": ["conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G"],
"monthly_budget_credits": 1000,
"per_order_max_credits": 500,
"budget": {
"monthly_credits": 1000,
"spent": 400,
"remaining": 600,
"resets_at": "2026-10-01T00:00:00Z"
},
"webhook_url": "https://agent.example.com/mydatahub/events",
"disclosure": "account"
},
"secret": "mdh_test_Example0000000000000000000000000"
}Errors
Every error is a problem object with a code and a next_step. Every response carries the RateLimit and RateLimit-Policy headers; see Rate limits.
| Status | Problem codes | When |
|---|---|---|
| 400 | invalid-request, idempotency-key-required, unsupported-version | The request could not be read, or an Idempotency-Key is missing. |
| 401 | authentication-required, account-required | Sign in (OAuth) or send an API key (authentication-required), or create a free account for what the anonymous tier does not cover (account-required). |
| 403 | insufficient-scope, confirmation-required, business-details-required, licence-acceptance-required, dpa-acceptance-required, agent-budget-exceeded | The token lacks a scope (insufficient-scope, with required_scope and a WWW-Authenticate step-up challenge); no person confirmed the spend and no agent key covers it (confirmation-required, with an approval_url); an agent key's per-order cap or monthly budget does not cover it (agent-budget-exceeded); an account made by self-serve sign-up has not yet confirmed its business name and that it is a UK business, which its first order needs (business-details-required); the order needs the Data Licence and Sharing Agreement accepted at the version in force (licence-acceptance-required); or a suppression upload comes before the organisation has accepted the Article 28 data processing agreement in force (dpa-acceptance-required). Nothing is charged. |
| 409 | idempotency-key-reused, quote-expired, order-not-ready, order-not-cancellable | An Idempotency-Key reused with different parameters, an expired machine quote, or an object in the wrong state. |
| 422 | validation-failed, quote-required, dataset-unavailable | The request is not valid. errors lists every problem with a JSON pointer and did_you_mean. |
| 429 | rate-limited | Too many requests. Wait for Retry-After. |
| Any other failure | internal-error, service-unavailable, tps-screen-expired | Something failed on our side (500 internal-error) or the service is briefly unavailable (503 service-unavailable, with Retry-After). Nothing was charged; retry with the same Idempotency-Key. A count, sample, quote or order with telephone in play is also refused with 503 tps-screen-expired, with no Retry-After, while the dataset version's TPS and CTPS screen is more than 28 days old. |