Are you building an internal app, a CRM or a Power Automate flow that sends email through Microsoft Graph? The ATOM Signatures API returns the HTML of an employee's signature, exactly as Outlook would insert it, so your automated emails carry the same signature as the ones written by hand. This page covers activation, authentication, routes, errors and limits, with examples you can copy.
/api/v1 that change no signature, contact details or assignment — four for signatures (whoami, signatures, signature, signature/send) and three for satisfaction survey results (feedback/*) —, authentication by API key, a single error format, and clear rate limits. The API is off by default: an administrator of your organization turns it on in Settings. The OpenAPI specification is public: /api/openapi.json.
How do you turn on the API?
The API is off by default. An administrator of your organization turns it on in the console, under Settings → “Developer API”. While it is off, every authenticated request receives a 403 API_DISABLED error.
The same card lets administrators create and revoke API keys.
The API is meant for server-to-server calls: it cannot be called from a browser, and a request carrying an Origin header from another site is refused (403). It does not change signatures, contact details or assignments; only the user's seat and the send counter are updated (see Seats and billing).
Authentication by API key
Every request carries an Authorization: Bearer atom_live_… header with an API key. It is the only authentication mode: any other credential is refused.
- Create it in Settings → “Developer API” → New key, and give it a name (for example “CRM integration”).
- It is shown only once, when you create it. Copy it into a secrets vault: ATOM keeps only a fingerprint and cannot retrieve it.
- An organization can have at most 10 active keys. Each key shows its creation date and last use; revocation is immediate.
- A key gives access to the signatures of every user in your organization: never put it in a web page or a code repository.
- It is independent of the Outlook add-in: renewing one does not affect the other.
Signature routes
Base URL: https://signatures.atomsolutions.ca. All responses are JSON and carry Cache-Control: no-store.
GET /api/v1/whoami
Checks the credentials and returns your organization and the name of the key used.
Response: { "tenant": { "id": "11111111-2222-3333-4444-555555555555", "name": "Example Firm Inc." }, "auth": "key", "keyName": "CRM integration", "apiEnabled": true }. tenant.id is your Microsoft 365 organization's identifier; auth is always key.
GET /api/v1/signatures — a user's signatures
Lists the signatures assigned to a user, so you can let someone choose which one to use. The user parameter follows the same rules as the preview (required, an address from a domain of your organization). Signatures are in priority order; only the ID and the name are returned, never the HTML.
Response: { "signatures": [ { "id": 12, "name": "…", "isDefault": true } ], "defaultSignatureId": 12 }. isDefault marks the signature that GET /api/v1/signature would return without signatureId or recipients; if none fits, defaultSignatureId is null. This call counts neither a seat nor a send.
A signature's ID is also in the console: the signatures list shows an “ID” column when the API is turned on, and clicking the ID copies it.
GET /api/v1/signature — preview
Returns a user's HTML signature, as Outlook would receive it. Parameters:
user: the user's address. Required, and it must belong to a domain of your organization. An address from another domain gives404 USER_NOT_FOUND.compose:new(default),replyorforward. A signature can have a shorter or different version for replies and forwards.recipients: the recipients' domains, separated by commas (50 at most), for signatures whose content depends on the recipient.signatureId: optional, the ID of a specific signature, taken fromGET /api/v1/signatures. Without it, the user's usual signature is returned. It must be assigned to the user, otherwise404 SIGNATURE_NOT_FOUND; a value that is not a positive integer gives400 INVALID_REQUEST.
The preview returns a signature you can use as is: it takes the user's seat (see Seats and billing), but counts no send.
Response: { "matched": true, "html": "…", "signatureId": 12, "name": "…", "mode": "full" }. The mode field tells which version was rendered (full, compact, custom or none); with none, html is empty and there is nothing to add to the message.
POST /api/v1/signature/send — signature for a send
Same response as the preview, with a JSON body { "user", "compose", "recipients", "signatureId" }. signatureId is optional: an integer or a string of digits, with the same rules as the preview. A refused signatureId (404) takes no seat. The difference is accounting: on top of the user's seat, this call counts the send, exactly like an email sent from Outlook (see Seats and billing). Call it once per email actually sent.
Satisfaction survey results
If your organization adds a satisfaction survey to its signatures, the API returns its aggregated results, for example to show them in an internal dashboard.
They return totals only: never a comment, a respondent's address or a result per employee or per signature. A result based on fewer than 10 responses is not broken down: it only contains "insufficient": true, with no count or value.
GET /api/v1/feedback/surveys
The organization's surveys: { "surveys": [ { "id": 12, "name": "…", "scale": "csat3", "status": "active", "created_at": "…" } ] }. The scale is csat3 (three faces, scores 1 to 3), nps (0 to 10) or stars5 (1 to 5 stars); the status is active, paused or archived.
GET /api/v1/feedback/summary — result for a period
Parameters:
survey: the survey's numeric identifier. Required. A survey that does not exist or belongs to another organization gives404 SURVEY_NOT_FOUND.from(inclusive) andto(exclusive): optional, in ISO 8601 format: a date (2026-10-01, read as UTC) or a date-time with a time zone (2026-10-01T00:00:00Zor2026-10-01T00:00:00-04:00). A date-time without a time zone is refused (400), as is a period wherefromdoes not come beforeto.
Response: { "survey": 12, "scale": "csat3", "insufficient": false, "count": 48, "value": 0.7916666666666666, "distribution": { "1": 3, "2": 7, "3": 38 } }. The meaning of value depends on the scale: for csat3, the share of “satisfied” scores (a fraction from 0 to 1, not rounded); for nps, the NPS score (-100 to 100, with promoters, passives and detractors); for stars5, the average star rating.
GET /api/v1/feedback/timeseries — trend over time
Same parameters, plus interval: day (default) or week. The response contains a series array, with one result per day or week that received responses (bucket = first day of the interval, Monday for a week, Eastern time). Each interval follows the same 10-response rule.
# List the surveys
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
https://signatures.atomsolutions.ca/api/v1/feedback/surveys
# Result of survey 12 since October 1, then week by week
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
"https://signatures.atomsolutions.ca/api/v1/feedback/summary?survey=12&from=2026-10-01"
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
"https://signatures.atomsolutions.ca/api/v1/feedback/timeseries?survey=12&interval=week"These routes use no seat and count no send.
Examples
With an API key in an environment variable:
# Check the key
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
https://signatures.atomsolutions.ca/api/v1/whoami
# Preview a user's signature
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
"https://signatures.atomsolutions.ca/api/v1/signature?user=marie.tremblay@example.ca"
# Signature for a real send
curl -s -X POST -H "Authorization: Bearer $ATOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"user":"marie.tremblay@example.ca","compose":"new"}' \
https://signatures.atomsolutions.ca/api/v1/signature/sendTo pick a specific signature, list the user's signatures first, then pass the chosen ID as signatureId:
# The signatures offered to the user
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
"https://signatures.atomsolutions.ca/api/v1/signatures?user=marie.tremblay@example.ca"
# Preview signature 12, then signature 12 for a real send
curl -s -H "Authorization: Bearer $ATOM_API_KEY" \
"https://signatures.atomsolutions.ca/api/v1/signature?user=marie.tremblay@example.ca&signatureId=12"
curl -s -X POST -H "Authorization: Bearer $ATOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"user":"marie.tremblay@example.ca","signatureId":12}' \
https://signatures.atomsolutions.ca/api/v1/signature/sendThe typical case: adding the signature to an email sent through Microsoft Graph. In Node.js (version 18 or later):
const res = await fetch('https://signatures.atomsolutions.ca/api/v1/signature/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ATOM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ user: 'marie.tremblay@example.ca', compose: 'new' }),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const { html: signature } = await res.json();
// Graph email body (POST /users/{upn}/sendMail)
const body = { contentType: 'HTML', content: `<p>Hello,</p>${signature}` };The OpenAPI specification describes every route and can be imported into a tool such as Postman or into a client generator.
Error codes
Every error has the same shape: { "error": { "code": "…", "message": "…" } }, with one exception: a request refused for an Origin header from another site (see the table).
| HTTP | Code | Meaning |
|---|---|---|
| 401 | INVALID_CREDENTIALS | Missing header, unknown or revoked key, a credential that is not an API key, or an inactive organization. The response is the same for all of these causes. |
| 403 | API_DISABLED | Valid credentials, but the API is not turned on in Settings. |
| 403 | — | Request carrying an Origin header from another site (a call from a browser). The body of this response is not in the error format above. |
| 400 | INVALID_REQUEST | Missing user, invalid compose or recipients, signatureId that is not a positive integer, unreadable JSON; for feedback/*: missing survey, invalid date or interval. |
| 413 | INVALID_REQUEST | Request body too large (over 500 KB). A body that cannot be read for another reason (unsupported encoding, for example) also gives INVALID_REQUEST, with the matching 4xx status. |
| 404 | USER_NOT_FOUND | User not found or without an assigned signature. The response is identical for a user of another organization. |
| 404 | SIGNATURE_NOT_FOUND | signatureId that is not assigned to this user. The response is the same for a nonexistent ID and for one from another organization. |
| 404 | NOT_FOUND | Unknown route under /api/v1. |
| 404 | SURVEY_NOT_FOUND | Survey not found. The response is identical for a survey of another organization. |
| 402 | BILLING_INACTIVE | The organization's subscription is not active. |
| 409 | SEAT_LIMIT | The seat limit is reached for this user. |
| 429 | RATE_LIMITED | Too many requests; the Retry-After header gives the delay in seconds. |
| 500 | INTERNAL_ERROR | Server-side error. |
What are the rate limits?
Over a one-minute window:
- 120 requests per API key;
- 600 requests per organization, all keys combined;
- 300 requests per IP address, before authentication.
Beyond that, the API answers 429 RATE_LIMITED with a Retry-After header. A signature rarely changes: cache the preview on your side and keep signature/send for emails actually sent.
Seats and billing
The model is the Outlook add-in's: a distinct user who sends signed email occupies one seat.
- The list (
GET /api/v1/signatures) counts nothing: it only returns IDs and names, never the HTML. - The preview (
GET /api/v1/signature) takes the user's seat, without counting a send. - The send (
POST /api/v1/signature/send) takes the user's seat and counts the send, like an email sent from Outlook. - A user already counted does not take a second seat. If the subscription's seat limit is reached for a user not yet counted, both the preview and the send answer
409 SEAT_LIMIT. - If the organization's subscription is not active, the response is
402 BILLING_INACTIVE.
For pricing and seat management, see pricing.
Frequently asked questions
Is the API turned on by default?
No. An administrator of the organization turns it on in Settings → “Developer API”. While it is off, every authenticated request receives the API_DISABLED error.
Who can create an API key?
Only the organization's administrators, in Settings. An organization can have at most 10 active keys. The key is shown only once, when it is created, and can be revoked at any time.
How does the API authenticate calls?
With an API key, created by an administrator in Settings and sent in the Authorization: Bearer header. It is the only mode: the key suits a script, a CRM or an automated flow, and can be revoked at any time.
Can the API change a signature?
No. It changes no signature, contact details or assignment, and nothing in your directory: it returns a user's signature. Only the user's seat and the send counter are updated.
Does the API use a seat?
Yes. Every signature returned takes the user's seat, preview (GET /signature) included; a user already counted does not take a second one. Only the send call (signature/send) also counts the send, like an email sent from Outlook. The signature list counts nothing.
How do I choose which signature is returned?
Call GET /api/v1/signatures?user=… to list the signatures assigned to the user, then pass the chosen ID as signatureId to GET /api/v1/signature or POST /api/v1/signature/send. Without signatureId, the API returns the user's usual signature. The ID can also be copied from the ID column of the console's signatures list, when the API is turned on.
Does the API give access to satisfaction survey responses?
Only to aggregated results: number of responses, score and score distribution, for a period or day by day. Never a comment, a respondent's address or a result per employee, and a result based on fewer than 10 responses is not broken down.
Where is the OpenAPI specification?
At https://signatures.atomsolutions.ca/api/openapi.json. It describes every route (signatures and satisfaction surveys), the error schemas and API key (Bearer) authentication.
Consistent signatures, even on automated emails. Turn on the API in Settings, create a key and fetch your employees' signatures in a few lines.
Learn more: dynamic signatures with Microsoft Graph and trust and security.