GET /api/v1/me says where the key stands, GET /api/v1/lists says
what there is to work on. Together they turn a key into a context, and every other endpoint of the
API becomes reachable without a human pasting an id out of the interface.
Authentication
Same key, same rules as everywhere else: anx-api-key header, one organization per key.
Authentication, roles, the response envelope and the full list of status codes are on the
API reference.
curl -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/me"member of the organization is enough.
Where the key stands
GET /api/v1/me
The first call of an integration. It answers the one question a key alone cannot: whose data am I
about to touch, and with what rights?
| Field | Description |
|---|---|
organization.id | The organization the key belongs to — the entire scope of the key |
organization.name | Its display name |
organization.slug | Its slug, the one that appears in the app URLs |
organizationRole | The role the key acts with: owner, admin or member |
keyId | The identifier of the key itself |
curl -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/me"{
"success": true,
"data": {
"organization": {"id": "…", "name": "Acme", "slug": "acme"},
"organizationRole": "admin",
"keyId": "…"
}
}The value of the key is never returned.
keyId names the key, it does not let anyone
authenticate with it — a /me response that leaks is an organization name that leaks, not a
credential. No endpoint of /api/v1 ever echoes a secret back.role is the useful half of the answer. A key made by a member can read and render, nothing more;
admin is what creating, tagging and sending require — the table is on the
API reference. Reading the role before the first write is how an agent finds out it
is read-only, rather than by collecting a 403 halfway through a run it cannot undo.
Telling a production key from a test one
A key carries no label saying which environment it belongs to. What it carries is an organization, and that is the thing to assert: compareorganization.slug — or organization.id — against what
your integration expects, and stop if they differ.
ORG=$(curl -s -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/me" | jq -r '.data.organization.slug')
[ "$ORG" = "acme-staging" ] || { echo "wrong organization: $ORG"; exit 1; }Finding a list
GET /api/v1/lists
The lists of the organization, paginated, each with the number of contacts it holds. This is where a
listId comes from: the contact endpoints all require one, and reading it off the app was until now
the only way to get it.
| Parameter | In | Required | Description |
|---|---|---|---|
page | query | no | Defaults to 1 |
limit | query | no | Defaults to 20, capped at 100 |
search | query | no | Filters by list name, partial and case-insensitive |
curl -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/lists?limit=50"{
"success": true,
"data": [
{
"id": "…",
"name": "Newsletter",
"contactCount": 1248,
"createdAt": "…",
"updatedAt": "…"
}
],
"pagination": {"total": 3, "page": 1, "limit": 50, "totalPages": 1}
}pagination is the same envelope as everywhere else — total counts the lists, not the contacts.
The organization is not a parameter and never will be: it comes from the key, so this endpoint can
only ever return your own lists.
contactCount is not the size of an audience. It counts every contact of the list, whatever
its status — unsubscribed, bounced and complained included. How many people a campaign would
actually reach is recipientCount on the campaign, which counts subscribers only — see
Content & Send.POST /api/v1/lists is
documented in Audience.
GET /api/v1/lists/{listId}
One list. Without contactCount : le détail ne le calcule pas, seule la collection le porte.
Si tu as besoin du compteur pour une seule liste, lis-le dans GET /api/v1/lists.
curl -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/lists/$LIST_ID"| Status | When |
|---|---|
200 | The list, without its contact count |
404 | Unknown id, or a list belonging to another organization |
A list of another organization answers
404, never 403. The two cases are indistinguishable
by design: a 403 would confirm that the id exists somewhere, which already says something about
an account that is not yours. Do not read this 404 as "wrong permissions" — read it as "not in
your organization", and stop looking.The bootstrap sequence
1
Find out where you are
curl -s -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/me" | jq '.data'role: member means every write below
will answer 403.2
Pick the list
LIST_ID=$(curl -s -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/lists" | jq -r '.data[] | select(.name == "Newsletter") | .id')POST /api/v1/lists, see Audience.3
Work the audience
curl -s -H "x-api-key: $MK_API_KEY" \
"https://agentsmail.io/api/v1/contacts?listId=$LIST_ID&status=subscribed"GET /api/v1/tags is the same trick as /lists, for tag ids.4
Compose and send
curl -s -X POST -H "x-api-key: $MK_API_KEY" -H "content-type: application/json" \
-d '{"name":"August newsletter","subject":"What shipped in August","templateId":"'$TEMPLATE_ID'","listId":"'$LIST_ID'"}' \
"https://agentsmail.io/api/v1/campaigns"/me and /lists never works from a stale id.