API
The REST API generated from your datastores — what it exposes, how callers authenticate, and the two gates that decide what is reachable.
API Overview
Every datastore you define gets REST endpoints. There is nothing to generate and no separate API project — the endpoints follow from the data model, the same way the forms and the audit trail do.
Where to find it
Architect Panel → Integration & Connections:
- API Server — the console — settings, keys and the request log
- OpenAPI Specification — the machine-readable description of what is exposed
- API Clients — outbound connections, which are a different thing
Architect Panel → Activity:
- Activity Log — what an API call changed
Inbound and outbound are different things
Worth settling immediately, because the vocabulary collides:
- The API and API Server are inbound — other systems calling you. Callers authenticate with an API key.
- API Clients are outbound — this platform calling somebody else's API.
They have separate configuration, separate logs and separate settings. If you are looking for the key a partner uses to reach you, it is not under API Clients.
The spec describes the running server
The OpenAPI specification is generated from the same module the API server uses to decide what a request may reach — one source, two callers.
That matters more than it sounds. A hand-maintained spec drifts from the server, and a spec that documents a path the server refuses — or omits one it serves — is worse than no spec at all. Here they cannot disagree, because they ask the same question of the same code.
Two gates decide what is reachable
A datastore must clear both:
- The system-table switch. Off by default, which blocks every one of the platform's own internal datastores.
- The sensitive list. Not switchable, ever. It stays blocked whatever the switch says.
The second is what makes the first safe to offer at all. Turning the switch on is a reasonable thing to want — it exposes reference data such as countries and currencies to an integration — but it is not a reasonable way to hand out password hashes, so the things that must never be reachable are not governed by a setting.
Permissions still apply
The API is not a way around your access rules. A key acts as an identity, and that identity's group permissions, field security and row-level access all apply exactly as they do in the interface.
This is why giving an integration its own identity with its own narrow permissions is worth the few minutes it takes: the API cannot see more than the identity behind it.
Everything is logged
Requests are recorded with the key, the datastore, the method, the outcome, how many rows came back and how long it took. That is what makes an integration diagnosable later, and it is on by default.
Start by reading the spec
Before writing any client code, look at the OpenAPI specification for the datastore you mean to use. It tells you what is exposed and in what shape — which is faster than inferring it from a successful call and safer than assuming.
Worked example
A partner needs read access to two datastores. They are given an identity in a group with read permission on exactly those two, an API key against that identity, and the OpenAPI spec. Nothing else in the system is reachable to them, and the request log shows precisely what they have called.
Recommendations
- Give every integration its own identity with the narrowest permissions that work.
- Read the spec before writing a client.
- Leave the system-table switch off unless something specific needs it.
- Keep inbound and outbound clear in your own notes — the names are confusing.
API Keys
Inbound callers authenticate with an API key. A key belongs to an identity, and inherits that identity's permissions.
Where to find it
Architect Panel → Integration & Connections:
- API Server — where keys are created and managed
- Agent Access (A2A) — agent access, which a key can be permitted to use
- AI Tool Access (MCP) — AI tool access, likewise
Architect Panel → Security:
- Permissions — the groups a key’s identity belongs to
What a key carries
- The identity it acts as, in the platform's usual three parts.
- A name, so you can tell your keys apart.
- Enabled, and an expiry date.
- Last used.
- Three capability flags: MCP, A2A and BI.
The key is not the permission
A key does not carry access of its own — it carries an identity, and that identity's group memberships decide what it can reach. Widening a key means changing the groups behind it, which is the same review as widening a person.
That is a good property: there is one permission model, not a separate one for machines.
The three capability flags
These are separate from permissions and gate whole subsystems rather than data:
- MCP — the key may be used for AI tool access.
- A2A — the key may be used for agent access.
- BI — the key may reach business intelligence.
They ship off. A key intended for an ordinary integration should not carry them, because they open routes that integration has no use for.
Name keys after what uses them
"Partner X order feed" rather than "key 3". Names are what make revocation possible: when you need to disable a key in a hurry, you need to know which one, and the alternative is disabling several and finding out what breaks.
Always set an expiry
A key with no expiry outlives the integration, the supplier relationship and often the person who created it. An expiry forces a review — and an integration whose key is about to lapse is one somebody has to confirm is still wanted.
Annual is a reasonable default. Diary the renewal so it does not lapse mid-trade.
Last used is your cleanup tool
A key unused for months is usually a decommissioned integration nobody told you about. Review the list periodically and disable what is idle — quietly, since a genuinely live caller will complain within a day and can be re-enabled.
Treat a key as a credential
It is equivalent to a password for the identity behind it. It should not travel by e-mail, live in a repository, or be shared between integrations — sharing destroys the whole benefit of per-integration keys, which is that the log tells you who did what.
Rotating
Create a new key, move the caller to it, confirm traffic on the new key in the log, then disable the old one. Deleting first produces an outage; running two briefly does not.
Worked example
An organisation runs eleven keys, each named for its integration and expiring annually on staggered dates. A review finds two unused for six months — both decommissioned projects — and disables them. When a partner reports a failure, the log identifies their key immediately because no two integrations share one.
Recommendations
- One key per integration, named after it.
- Always set an expiry, and diary the renewal.
- Leave the capability flags off unless the key needs them.
- Rotate by overlapping, never by deleting first.
What Is Reachable
Not every datastore is reachable over the API. Two gates decide, and only one of them is a setting.
Where to find it
Architect Panel → Integration & Connections:
- OpenAPI Specification — the authoritative list of what is exposed
- API Server — the settings, including the system-table switch
Architect Panel → Data:
- Datastores — the datastores themselves
Gate one: the system-table switch
The platform's own internal datastores are distinguishable from yours. With the switch off — the default — none of them is reachable at all.
Turning it on is a legitimate thing to want: it makes reference data such as countries, currencies, languages and icons available to an integration, along with some reporting metadata. Turn it on for that, not as a general loosening.
Gate two: the sensitive list
A hardcoded set that stays blocked whatever the switch says. It is not configurable, and that is deliberate — it is the reason the switch is safe enough to offer.
What it protects, and why each one matters:
- Credentials and tokens — API keys themselves, OAuth access and refresh tokens and their signing keys, SCIM bearer tokens, passkeys, and two-factor seeds. A TOTP seed is password-equivalent.
- User records — password hashes, reset tokens, sign-in links, and live sessions.
- External system credentials — database hostnames and passwords, data source credentials for every driver, Azure, Google and Xero client secrets, accounting feed tokens, EDI certificates and private keys, deployment targets.
- Financial instructions — bank mandates, where a reference is an instruction to collect money.
- Your defences — blocked addresses and accounts, which map what you are protected against; which sign-in methods exist; directory topology.
- Execution surfaces — custom PHP callbacks, the extension registry, and the task scheduler, which runs whatever it is told to.
Two of those are worth dwelling on
The block list is readable data, not credentials — and it is still blocked, because a list of what you block tells an attacker what you have noticed.
The scheduler is blocked because it is not data at all: it is a list of things the platform will execute. Write access there is remote code execution with extra steps.
Check what an integration can actually see
Do not reason about it from the gates — read the OpenAPI specification as that key. It is generated from the same logic the server enforces, so it is the honest answer, and it takes a moment.
Permissions narrow it further
Clearing both gates makes a datastore reachable in principle. Whether a particular key can see it still depends on the permissions of the identity behind it, at datastore, field and row level.
So the practical answer to "what can this integration see" is the intersection of the gates and the identity — and the second is the one you control per integration.
If something you need is blocked
Where a system datastore is genuinely required, the switch is the supported route. Where something on the sensitive list is required, the answer is that it is not available and the requirement needs rethinking — usually it turns out that a derived view of the data, in a datastore of your own, is what was actually wanted.
Worked example
An integration needs currency codes and a customer list. The system-table switch is turned on so currencies are reachable, and the integration's identity is granted read on the customer datastore and nothing else. Its OpenAPI spec lists exactly those two, which is checked before the partner writes a line of code.
Recommendations
- Read the spec as the key rather than reasoning about the gates.
- Turn the switch on for a reason, not as a default.
- Do not try to route around the sensitive list — model what you need instead.
- Narrow by identity, which is the control you own.
Rate Limiting
The API can cap how much one caller may do in a window. It ships uncapped, which is how the API has always behaved.
Where to find it
Architect Panel → Configuration:
- Site Settings — requests allowed per window, window length and counter resolution
Architect Panel → Integration & Connections:
- API Server — the request log, where throttling shows up
Architect Panel → Security:
- Blocked IP Addresses — where repeated failed authentication is handled instead
The three settings
- Requests allowed per window — ships at 0, meaning unlimited.
- Window length — ships at an hour.
- Counter resolution — ships at a minute.
Keyed on the API key, not the address
This is the design decision worth understanding. The allowance belongs to the key.
Several callers legitimately share an address — a partner behind one gateway, several services in one data centre, everything behind a corporate connection. If the limit were per address, one badly behaved caller would exhaust the allowance and take the others down with it, and the victims would have no idea why.
Per key, a runaway integration throttles itself and nobody else.
It counts successful requests
Not failures. Repeated failed authentication is a different problem and is already handled by address-level blocking, which is the right tool for it — you do not want an attacker able to exhaust a legitimate key's allowance by failing against it.
Counter resolution
How finely the sliding window is counted. A finer resolution is more accurate and writes more counter rows; it must divide into the window, and is corrected automatically if it does not. Leave it alone unless you have a reason.
Should you turn it on?
Unlimited is fine for a small number of known integrations you control. A limit earns its place when:
- Third parties you do not control have keys.
- An integration has previously misbehaved — a retry loop, a poll that got faster.
- You want a runaway caller to degrade rather than to take a database with it.
Set it generously
Look at the request log first and find what your busiest legitimate integration actually does in an hour, then set the limit well above it. A limit that occasionally catches normal traffic produces intermittent failures at the caller's end that are miserable to diagnose from the other side.
The purpose is to bound a runaway, not to ration ordinary use.
Tell integrators the limit
If third parties have keys, publish the number. A caller who knows the limit can pace themselves; one who does not will discover it as random failures and will assume your API is unreliable.
Watch for it in the log
When an integration reports intermittent problems, check whether it is hitting the limit before investigating anything else. It is a quick check and it explains a class of failure that otherwise looks like flakiness.
Worked example
An organisation with four partner integrations reviews a month of logs: the busiest averages 400 requests an hour, peaking near 900. The limit is set to 5,000 an hour — far above any legitimate use, low enough to stop a runaway. Each partner is told the figure. Six months later a partner's retry loop hits it, throttles, and affects nobody else.
Recommendations
- Set a limit once third parties hold keys.
- Base it on observed traffic, generously.
- Publish the limit to anyone with a key.
- Check the log for throttling before diagnosing flakiness.