The Footer365 API
How your own systems call the Footer365 API with an API key, what version 1 can do today, the rules a change to someone's details follows, and the rate limits and errors to expect.
The Footer365 API lets your own systems read and change the details Footer365 holds for your people, read each person's signature, and read and run your templates, rules and campaigns, without anyone signing in to the portal. Resellers use it to read their customers and seats. It is for an HR system keeping details up to date, an intranet page showing signatures, or a script. This article is for whoever builds that integration.
Before you start
You need an API key, made by someone with Manage developers on Developers > API keys in the portal. See Create and revoke API keys. Give the key only what the integration needs.
Calling the API
The API is at https://api.footer365.net, and every address starts with the version, /v1. Send the key in the Authorization header as a bearer token, over https:
curl https://api.footer365.net/v1/me -H "Authorization: Bearer f365_live_ten_..."
The same call in PowerShell:
Invoke-RestMethod https://api.footer365.net/v1/me -Headers @{ Authorization = "Bearer f365_live_ten_..." }
Call /v1/me first. It needs no particular scope and answers with your organisation, the key's name and prefix, the scopes it holds and its rate limits, which proves the key works.
A few rules apply to every call:
- https only. A call over plain http is refused. If a key has ever been sent over http, revoke it.
- Never put the key in the address. A key in the query string is refused, because addresses end up in logs. If one has been sent that way, revoke it.
- Your organisation only. A key acts for the organisation that made it, and nothing in a request can change that. A person who is not in your directory is simply not found.
- JSON in and out. Times are in UTC, written like
2026-10-19T09:15:00Z. New fields may be added to a response at any time within a version, so ignore any you do not recognise.
What version 1 can do
| Call | Scope | What it does |
|---|---|---|
GET /v1/me |
any key | Describes the key and your organisation. |
GET /v1/health |
none | Says the service is answering. Needs no key. |
GET /v1/users |
directory.read |
Lists the people and mail-enabled groups in your directory, ordered by address, 50 at a time by default and up to 200 with ?limit=. Pass the nextCursor from one page as ?cursor= to get the next. |
GET /v1/users/{email} |
directory.read |
One person or group, by primary address or Footer365 id, with every field a signature can use. |
PATCH /v1/users/{email} |
directory.write |
Sets or clears details held in Footer365 for that person. |
GET /v1/users/{email}/signature |
templates.read |
The signature the person's rules give a message, as HTML and as plain text. |
GET /v1/templates |
templates.read |
Lists your templates by name, with whether each is active and whether an enabled rule sends it. |
GET /v1/templates/{id} |
templates.read |
One template with the HTML and plain text of its current version, merge tags as written. |
GET /v1/rules |
rules.read |
Lists your signature rules in priority order: who each is for, which messages and recipients, and the template it sends. A rule that chooses between several templates at random also lists them, with their weights, in randomTemplates. |
GET /v1/rules/{id} |
rules.read |
One rule. |
POST /v1/rules/{id}/enable |
rules.write |
Turns a rule on, as its switch on the Rules page does. |
POST /v1/rules/{id}/disable |
rules.write |
Turns a rule off. |
GET /v1/campaigns |
campaigns.read |
Lists your campaigns, newest first, with their status, dates, target rules and clicks. |
GET /v1/campaigns/{id} |
campaigns.read |
One campaign. |
POST /v1/campaigns/{id}/pause |
campaigns.write |
Pauses a running campaign, as Pause on the Campaigns page does. |
POST /v1/campaigns/{id}/resume |
campaigns.write |
Turns a paused campaign back on, as Activate on the Campaigns page does. |
PUT /v1/recipient-lists/{id}/entries |
lists.write |
Replaces the addresses and domains a recipient list holds. |
Templates, rules and campaigns come back whole, in one answer, rather than a page at a time. Templates, rules and campaigns are not created or edited through the API yet; that stays in the portal.
The full description of every call, generated from the service itself, is at https://api.footer365.net/v1/openapi.json. Most API tools can import it. The same reference, with guides to keys, limits, errors and webhooks, is on the developer pages.
Keeping a recipient list in step
A recipient list is a named set of addresses and domains that rules, disclaimers and campaigns can target. See Keep lists of recipients to reuse. A system that already holds the addresses, such as a CRM, can keep a list in step with a scheduled call. The list is made in the portal, and its id is on its page under Id for the API.
curl -X PUT https://api.footer365.net/v1/recipient-lists/3f2c1b66-6a27-4a0e-9a0e-7a2b5a2c9b11/entries \
-H "Authorization: Bearer f365_live_ten_..." -H "Content-Type: application/json" \
-d "{ \"addresses\": [ \"[email protected]\" ], \"domains\": [ \"fabrikam.example\" ] }"
The call replaces what the list holds, as Replace the list with this file does when a file is imported in the portal. Send everything the list should hold each time.
- A list holds up to 100,000 entries. More than that is refused with
400and the codelist_too_large, and the list is left as it was. - A value that is not an address or a domain, a wildcard, and a value sent twice are left out and counted. The answer gives
used,addresses,domainsandnotUsedas numbers, and never repeats an address. - Up to 3,000 entries are written before the answer. A larger list is written in the background,
queuedistrue, and the list is used as it was until it is done. - Each address is kept only as a one-way code, so there is no call to read a list back, and no
lists.readpermission. Keep your own copy. - A key made by an administrator limited to part of the organisation is refused with
403and the codelist_organisation_wide, because a list belongs to the whole organisation. - Every change is recorded in the audit log under the key's name and prefix, with how many entries there were before and after, never an address.
Hearing about changes
Rather than asking again and again whether something has changed, your system can be told: a webhook posts to an address of yours when a person's details change, a template is published or a campaign starts or ends. See Send events to your systems with webhooks.
A person's details
Each field of a person comes back with:
value: what signatures use nowdirectoryValue: what Microsoft 365 holds, if anythingfooter365Value: what is held in Footer365, if anythingreplacesDirectory: whether the Footer365 value is used instead of the Microsoft 365 onesource: where the Footer365 value came from (portal,import,stafforsource), who set it and when. A value a directory source set showssource, with the source's name as who set it
The field names are the merge tags used in templates, such as JobTitle, MobilePhone or LinkedIn, plus your own custom fields, which can also be named by their name.
Changing details
A change names the fields to set:
curl -X PATCH https://api.footer365.net/v1/users/[email protected] \
-H "Authorization: Bearer f365_live_ten_..." -H "Content-Type: application/json" \
-d "{ \"fields\": { \"MobilePhone\": \"07700 900123\", \"WorkingHours\": \"Mon to Thu, 8am to 6pm\" } }"
It follows exactly the rules of the spreadsheet on Directory > User details:
- Nothing is ever written to Microsoft 365.
- A field Microsoft 365 already holds is changed only if your organisation has set that field to Replaces the directory value on User details. Otherwise the field only fills a gap.
- A value a person set for themselves through staff self-service is theirs, and is left alone.
- A value for a custom field must fit the field's type.
nullor an empty string clears a value held in Footer365, so the Microsoft 365 value, if there is one, is used again.
Nothing is changed unless every field in the request can be. If any cannot, the answer is 422 with each one named in errors and the reason beside it. A value set through the API shows in the portal as imported, under the key's name, and every change is recorded in the audit log under the key's name and prefix. See Import user details.
If your HR system cannot call an API but can publish a CSV or JSON file at an https address, Footer365 can fetch the file on a schedule instead, so nothing has to push. A value from a directory source follows the same rules as one set through the API, and the latest value wins. See Keep details up to date from a file.
A person's signature
GET /v1/users/{email}/signature builds the signature the person's rules give a new email to people outside your organisation, from their details as they stand now. Add ?type=reply or ?type=forward, and ?recipients=internal, for the other kinds of message. Campaign banners and disclaimers are left out, because they belong to a message rather than to the person. If no rule gives the person a signature for that kind of message, the answer is 404 with the code no_signature.
Templates, rules and campaigns in folders
A key reaches templates, rules and campaigns as someone holding View signatures (for a read scope) or Manage signatures (for a change scope) would, fixed when the key was made. Anything in no folder, or in a folder that follows the Signatures permissions, is open to it on those terms.
A folder with its own access list names people and groups, and a key is neither, so such a folder is closed to every key unless its list gives Everyone with View signatures access, and then only to the level that entry gives. Something in a closed folder is not found (404); something in a folder the key can see but not change is refused with 403 and the code folder_read_only. To let an integration work in a folder, give Everyone with View signatures access to it, or keep what it works on outside folders with their own access. See Folders and who can use them.
Keys made by an administrator limited to part of the organisation
An administrator limited to part of the organisation by a scope makes keys that carry that scope, so a key reaches no further than its maker. See Limit an administrator to part of the organisation. GET /v1/me names the part in limitedTo, which is null for a key that reaches the whole organisation. For a limited key:
- People.
GET /v1/userslists only the people in the scope, and a person outside it is not found by any call that names them (404,user_not_found), exactly as one who is not in your directory is. That includes a change to their details and their signature. - Rules.
GET /v1/rulesstill lists every rule the key can see by folder, so a head office rule that wins for its people is in sight, but a person outside the scope that a rule names comes back with a nullvalueandid. - Changing a rule or a campaign. Turning a rule on or off, or pausing or resuming a campaign, works only when it is aimed wholly inside the scope. For any other the answer is
403with the coderule_outside_scopeorcampaign_outside_scope, and the attempt is recorded in the audit log under the key's name. - Clicks.
clicksis null for a campaign aimed at people outside the scope, because the count includes them. - Recipient lists. A limited key cannot change a list: the answer is
403with the codelist_organisation_wide.
Turning rules on and off, pausing and resuming campaigns
These calls do exactly what the buttons in the portal do, and each change is recorded in the audit log under the key's name and prefix, with the status before and after. Asking for what is already so changes nothing and answers 200 with changed set to false, so a call can safely be repeated.
- A rule turned off gives nobody a signature until it is turned on again.
- Only a running campaign can be paused (
409withcampaign_not_runningotherwise), and only a paused one resumed (409withcampaign_not_paused). - Resuming is checked as Activate is: a campaign stopped because its link is listed as unsafe, or any campaign while your organisation is flagged for unsafe links, is refused with
422and the codecampaign_refused. Its links are checked with Google first when that was not done in the last day, and if one cannot be checked just then the campaign still resumes, with anoticesaying so.
For resellers and distributors
A reseller or distributor reads its customers through the same API with a reseller key, which starts f365_live_res_. Keys are made in the reseller portal under Account > API keys, by someone who can change the reseller's settings, and a key can only read customers if its maker holds View customers. A reseller key reaches none of the calls above, and an organisation's key none of these:
| Call | Scope | What it does |
|---|---|---|
GET /v1/customers |
customers.read |
Lists your customers by name, with each one's subscription, billing and seats. Add ?search= to narrow it by name or domain. |
GET /v1/customers/{id} |
customers.read |
One customer, by its Footer365 id. |
GET /v1/customers/{id}/seats |
customers.read |
The seats its subscription is for, a change to them already scheduled, and the user mailboxes counted in its directory at the last sync. |
A key reaches the same customers as the reseller portal's customer list: a reseller's own, and for a distributor its resellers' customers too, but never the account's own tenant. A customer that has not yet set Footer365 up, and so not yet accepted the reseller, is left out of the list and not found by its id. The same list can be downloaded from the reseller portal with Export CSV.
Rate limits
Each key may make 600 reads and 120 changes a minute, and each address 1,200 calls a minute whatever key it sends. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit, the answer is 429 with a Retry-After header saying how many seconds to wait. Give a busy integration a key of its own, so it cannot use up another's allowance.
Errors
An error comes back as application/problem+json with a code that stays the same, so test for the code rather than the wording:
| Status | Code | Meaning |
|---|---|---|
| 400 | https_required, key_in_query, no_fields, invalid_limit, invalid_cursor, invalid_query, no_entries, no_usable_entries, list_too_large |
The request itself is wrong. |
| 401 | missing_key, invalid_key, key_revoked, key_expired, address_not_allowed, account_closed, account_inactive, api_not_available |
The key was not accepted. api_not_available means the public API is not available to your organisation at the moment; the key is kept and works again once it is. |
| 403 | scope_missing, folder_read_only, rule_outside_scope, campaign_outside_scope, list_organisation_wide, reseller_key_required, not_available |
The key does not hold the scope the call needs, which the message names, may not change what is in that folder, or was made by an administrator limited to part of the organisation and the rule or campaign is aimed beyond it, or the call changes a recipient list, which belongs to the whole organisation. not_available means that part of the API, such as campaigns or recipient lists, is not available to your organisation at the moment; nothing it set up is lost. |
| 404 | user_not_found, no_signature, template_not_found, rule_not_found, campaign_not_found, recipient_list_not_found, customer_not_found |
Nothing to return. |
| 409 | campaign_not_running, campaign_not_paused, list_not_changed |
The campaign is not in a status the call can be made from, or the recipient list could not be changed just now, for example because it is being imported; the list is left as it was. |
| 422 | fields_refused, campaign_refused |
A change could not be made; see errors or the message. |
| 429 | rate_limited |
Too many calls; wait for Retry-After seconds. |
| 500 | save_failed |
A change could not be saved, so nothing was changed. Try again. |
| 503 | keys_unavailable |
Keys cannot be checked at the moment. Try again in a few minutes. |
Every answer carries an X-Request-Id. Quote it if you contact support about a call.
Related articles
Still stuck?
Tell us what you were trying to do and what you saw. Include the sender, a recipient and roughly when a message was sent, and we can trace it.