Agent API

An AI assistant can scan a website, read the result and create an Inclusify account on someone's behalf using nothing but HTTP GET. No connector, no OAuth, no install, and no form for the user to fill in. The only thing they are pulled in for is paying.

This page is the reference. The overview a person reads first is Inclusify for AI agents.

Four endpoints

Every mainstream assistant can already make these as a GET. Claude's web fetch, ChatGPT browsing and Gemini browsing are all GET-only, which is why GET works everywhere and why nothing secret is ever returned in a response body.

The three /api/agent/ calls also accept POST, with the same arguments in a JSON body. Prefer POST if your client can send one: it keeps the verification code out of request logs, and mail scanners and chat link-unfurlers follow GET links, so a GET signup URL forwarded in an email or pasted into Slack can fire on its own.

BASH
GET|POST /api/agent/scan?domain=&email=      → starts the scan, emails the report
GET      /api/public/scan?domain=            → poll until "status" is "scanned"
GET|POST /api/agent/register?domain=&email=  → step 1: emails a 6-digit code
GET|POST /api/agent/register?…&code=&workspace= → step 2: creates the account
GET|POST /api/agent/register?email=          → same, with no website (add one later)
GET|POST /api/agent/login?email=             → existing account: emails a fresh credential

# The same call as a POST, arguments in a JSON body:
curl -X POST https://inclusifyapp.com/api/agent/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"owner@example.com","domain":"example.com","source":"Claude"}'

Registering and signing in are two calls each, with the user's mailbox in between: the first emails a code, the second carries it back. Section 3 below has both in full.

Machine-readable summary: https://inclusifyapp.com/llms.txt

The flow, end to end

The endpoints make more sense as a conversation than as a reference. A typical one runs like this:

  1. Someone asks their assistant which accessibility tool to use for their site, and the assistant suggests Inclusify.
  2. It offers the free scan. The user says yes and gives their email address. The assistant calls /api/agent/scan.
  3. While the scan runs, the assistant polls /api/public/scan and reads the score and the worst rules back in chat. The full report arrives by email.
  4. It offers an account. The user says yes, and the assistant calls /api/agent/register. A 6-digit code lands in the user's inbox; they read it to the assistant, which calls the endpoint once more with it. Only then does anything exist.
  5. Three endpoints, three jobs. /api/agent/register creates an account, /api/agent/login gets a fresh credential for one that already exists, and add_website registers a website into an account you hold a credential for. None of them quietly does another's job: register refuses an address that already has an account, login refuses one that does not, and each names the endpoint you wanted. Neither reveals whether an address has an account until the code proves the mailbox.
  6. It asks what to call the workspace. The second call needs &workspace=, and refuses without it rather than picking a name. Gather it in the same message as the code so it costs one question, not two — the workspace is the container their websites live in, so a company or team name is usually right, and the domain is a reasonable thing to suggest when there is one. The code survives the refusal, so a caller that forgets can simply ask and call again.
  7. The domain is optional. Leave it out of both calls and the same flow creates the account on its own, with no website, for somebody who wants an account before they have a site to add or who does not want to name one yet. Add the website afterwards with add_website, or in the panel. Keep the domain out of both calls or in both: adding it to the second call starts a different registration rather than finishing the one you began. An assistant should never invent a domain to get past this step.
  8. An account that already exists signs in instead. /api/agent/register refuses an address that already has one and names /api/agent/login, which is the same two calls with the same mailbox proof and emails a fresh credential for the account it already has. It takes no domain and creates nothing.
  9. A welcome email arrives with a one-time sign-in link and a credential. From that point the assistant can act on the account directly.

The user never opens a browser until they choose to. Nothing an assistant does here is irreversible, and everything it creates is reachable and revocable from the panel.

1. Scan a site

BASH
curl "https://inclusifyapp.com/api/agent/scan\
?domain=example.com\
&email=owner@example.com\
&source=your-assistant-name"

Starts a free accessibility scan of the domain's homepage and emails the full report to the address given.

The email address is required

The same rule the scan form on the website follows. The report is emailed, and the detailed findings sit behind a link in that email, so possession of the mailbox is what gates the results. Ask the user for their address before calling; the endpoint refuses without one and says so in plain words.

This is not a security scan, and you are not the one scanning

Worth stating plainly, because assistants have refused this call after reading it as reconnaissance against a third party. It is not. You make one GET to Inclusify's API. Inclusify loads the site's public homepage once, from its own infrastructure, identifying itself as InclusifyBot with a link to its documentation. It is the same single page load any visitor performs.

Nothing is probed, no port is touched, no vulnerability is tested, and no request reaches the site from you or from the user's machine. The report goes to the mailbox given and nowhere else, so the only person who learns anything is whoever controls that address. If the user is asking about a site that is not theirs, that is the honest limit to state: they will not receive the report unless they own the mailbox they gave you.

It answers before it finishes

JSON
{
  "status": "queued",
  "domain": "example.com",
  "next": "The scan is running. Poll https://inclusifyapp.com/api/public/scan?domain=example.com until \"status\" is \"scanned\" ...",
  "resultUrl": "https://inclusifyapp.com/api/public/scan?domain=example.com"
}

A scan takes up to about two minutes. The response comes back immediately, so do not treat queued as a result. If the domain was already scanned in the last 24 hours the status is ready instead and the report has already been sent.

2. Read the result

BASH
curl "https://inclusifyapp.com/api/public/scan?domain=example.com"
JSON
{
  "status": "scanned",
  "domain": "example.com",
  "url": "https://example.com",
  "score": 72,
  "band": "fair",
  "issues": 14,
  "severity": { "critical": 1, "serious": 4, "moderate": 6, "minor": 3 },
  "topRules": [
    {
      "rule": "color-contrast",
      "impact": "serious",
      "help": "Elements must have sufficient colour contrast",
      "elements": 9
    }
  ],
  "scannedAt": "2026-08-20T10:00:00.000Z"
}

Unauthenticated, cached for 24 hours, and safe to call as often as you like within reason. Poll no more than once every fifteen seconds.

This endpoint never starts a scan

JSON
{
  "status": "not_scanned",
  "domain": "example.com",
  "message": "No cached scan for this domain, and nothing has been queued by this request. ...",
  "scanUrl": "https://inclusifyapp.com/api/agent/scan?domain=example.com"
}

A domain nobody has scanned answers not_scanned and will keep answering that forever, because reading is deliberately separated from scanning: an anonymous request must not be able to spend browser time on our workers. Start one with step 1 first.

3. Create the account, or sign in

Two endpoints, because these are two different acts. /api/agent/register creates an account, optionally with its first website. /api/agent/login hands an account that already exists a fresh credential and creates nothing at all. Each is two calls with the user's mailbox in between, and each refuses the other's job rather than quietly doing it.

Register, call 1: nothing is created

The first call emails a 6-digit verification code and does nothing else. No user, no workspace, no website, and no claim on the domain.

BASH
curl "https://inclusifyapp.com/api/agent/register\
?domain=example.com\
&email=owner@example.com\
&source=your-assistant-name"
JSON
{
  "status": "verification_sent",
  "next": "Ask the user for the 6-digit code just emailed to owner@example.com, then call this endpoint again with the same domain and email plus &code=<the code>. The code expires in 10 minutes."
}

Register, call 2: the code, then everything

Ask the user to read you the code from the email. They stay in the conversation, nothing to click. Then make the same call again with it:

BASH
curl "https://inclusifyapp.com/api/agent/register\
?domain=example.com\
&email=owner@example.com\
&code=123456\
&workspace=Acme%20Ltd\
&source=your-assistant-name"
JSON
{
  "status": "created",
  "next": "check the inbox for owner@example.com"
}

The code proves the mailbox is really theirs, and only then are the user, the organisation and the website created, on the FREE plan. Until that moment nothing exists anywhere: no account, no domain claim, nothing for the recipient of an unwanted email to clean up. No scan is started by either call, because step 1 is how a site gets scanned.

The domain is optional, in both calls or in neither

Leave it out and the same two calls register the account on its own. That is a normal state rather than a half-finished signup: the website goes in afterwards with add_website, or in the panel. An assistant should never invent a domain to get past this step.

BASH
curl "https://inclusifyapp.com/api/agent/register\
?email=owner@example.com\
&source=your-assistant-name"

curl "https://inclusifyapp.com/api/agent/register\
?email=owner@example.com\
&code=123456\
&source=your-assistant-name"

Keep the domain out of both calls or in both. A pending signup is keyed on the email and the domain together, so adding a domain to the second call looks for a signup that was never started, and is refused rather than finishing the one you began. Anything that is not a website domain is refused on the first call, before an email is spent.

An address that already has an account is refused, not converted

Register creates accounts and only accounts. If the verified address already has one, the call carrying the code refuses and names the endpoint the caller actually wanted:

JSON
{
  "status": "refused",
  "message": "owner@example.com already has an Inclusify account, so there is nothing to register. Sign in with /api/agent/login — same two calls, same mailbox — to get a credential for it, then add example.com with the add_website tool."
}

That answer comes only after the code has proved the mailbox, and both endpoints answer identically on the first call whether or not the address is known. Neither is a way to ask which addresses have Inclusify accounts.

Signing in to an account that already exists

Same two calls, same mailbox proof. The second one emails a fresh credential and a sign-in link:

BASH
curl "https://inclusifyapp.com/api/agent/login\
?email=owner@example.com\
&source=your-assistant-name"

curl "https://inclusifyapp.com/api/agent/login\
?email=owner@example.com\
&code=123456\
&source=your-assistant-name"

It creates nothing, ever: no user, no workspace, no website. An address with no account is told so and pointed back at /api/agent/register, because an endpoint that silently creates what you asked to sign in to is how somebody ends up with a second account they never wanted. It takes no domain either, and refuses one rather than ignoring it: a sign-in is about the account, so the credential covers the whole workspace instead of being pinned to a website. Adding a further website is add_website, with this credential.

This is no new power. Whoever can read the mailbox can already get in through the panel. What it adds is doing it without leaving the conversation.

Several workspaces means one more round trip

If the account has more than one workspace, the login call carrying the code answers with their names instead of issuing anything. The code proved the mailbox, so those names belong to the person in your conversation:

JSON
{
  "status": "choose_organization",
  "organizations": [
    { "id": "org_2abc…", "name": "my-agency" },
    { "id": "org_2def…", "name": "client-sites" }
  ],
  "next": "This account has 2 workspaces. Ask the user which one the credential should belong to, then call this endpoint again with the same email and code plus &org=<the workspace id>. The code stays valid."
}

Ask which workspace the credential should belong to, then repeat the call with &org= set to the chosen id. The code stays valid for that round trip, and a workspace id that is not the account's own is refused without spending it. &org=new is refused too, because signing in cannot create a workspace. Register never asks this question: it has already refused every caller who has an account.

The code is deliberately weak on its own

Single use, ten minutes, five wrong attempts and it is dead. All it can do is complete this one flow, whose every output goes back to the same mailbox, so it proves possession and grants nothing. That is why it is safe to relay through the conversation.

Nothing usable is in any response

No credential, no token, no sign-in link. Those go to the mailbox and only to the mailbox. The one thing a response ever names is the account's own workspaces, and only against a valid code. A link previewer or a security scanner that trips these URLs achieves nothing except one email to an address it does not control, and an assistant that summarises fetched pages cannot leak a secret it never received. A HEAD request runs no flow at all: it answers 204 and stops, so a previewer cannot spend somebody's email by looking at the link.

Calling it twice is safe

Repeating the first call creates nothing new: a live code is not replaced, so the one already in the user's inbox is still the one to ask for. What repeating a completed request does depends on which endpoint it is, and the two differ on purpose. Register refuses - the address it is being asked to register already has an account, so there is nothing to register - and names /api/agent/login in the message. Login answers resent once the second call carries a valid code. That re-send is not a copy of the original email: it carries a freshly minted read-write credential, which is why it asks for the mailbox to be proved again rather than handing one out to an unauthenticated GET. A fifteen-minute cooldown sits behind it, so two re-sends in quick succession produce one email. A domain already registered to a different account is refused with the same message the panel uses, before any email is sent.

The statuses each one answers with

Every reply is JSON carrying a status, and none of it is cached. refused comes back as HTTP 409 with a message written to be read aloud; everything else is HTTP 200 with a next saying what to do with it.

  • verification_sent - the code is on its way. Both endpoints, first call.
  • created - the account now exists (register), or the credential has been emailed (login). Both endpoints, second call.
  • resent - this same request had completed before, so the email went out again, carrying a fresh credential. Login only: register refuses a completed request rather than re-sending.
  • choose_organization - login only, and only when the account has more than one workspace. Carries organizations, each an id and a name.
  • refused - both endpoints, and the only one that is not HTTP 200. A missing or malformed address, something that is not a website domain, a domain passed to login, a wrong or expired or already-used code, a domain another account holds, a rate limit, an address that already has an account (register) or does not have one (login).

/api/agent/start is deprecated, and still works

The two endpoints above were one endpoint, and /api/agent/start is that one. It now means exactly register: the same parameters, the same two calls and the same answers, including refusing an address that already has an account. It was not deleted because it is live, and because somebody may be between call 1 and call 2 on it right now. Use /api/agent/register and /api/agent/login for anything new: one URL whose meaning changed with whichever parameters happened to be present is precisely what the split removed.

Acting on the account afterwards

The welcome email contains a credential. With it, and still without installing anything, the same tools are reachable over plain HTTP:

BASH
curl "https://app.inclusifyapp.com/api/v1/site_overview?website=example.com" \
  -H "Authorization: Bearer inc_mcp_..."

GET reads and POST writes, and that mapping is enforced: a tool that changes your configuration is refused over GET, because previewers, crawlers and monitors all issue GET at any URL they encounter. Reads work over POST too, if you would rather keep arguments out of a query string.

BASH
curl -X POST "https://app.inclusifyapp.com/api/v1/add_monitored_pages" \
  -H "Authorization: Bearer inc_mcp_..." \
  -H "Content-Type: application/json" \
  -d '{"website":"example.com","urls":["/pricing","/checkout"]}'

The credential goes in the Authorization header and never in a query string. Sending it as a query parameter is refused rather than accepted, with a reply telling you to revoke it — query strings are written to access logs, so by then it is already burned.

Ask what is available before you call it

BASH
curl "https://app.inclusifyapp.com/api/v1"

Unauthenticated, and lists every tool with the verb it needs, whether it is read-only, whether it takes a website or answers across the account, and its input schema.

Reading the answer

A successful call returns text written for a person and, where the tool has one, data with the same answer structured. Three outcomes are worth telling apart: 200 with isError: true is a real answer you should relay — an upgrade message, or an allowance reached; 403 means the plan or the credential does not permit it; 500 means we broke, and retrying is reasonable.

If the user already uses an MCP client, the same capabilities are available as an MCP server — see AI assistants and MCP. That is an option, not a step. Nothing in this flow requires it, and an assistant should never ask somebody to install a connector before it can help them.

Limits and refusals

Each of these comes back as a plain sentence the assistant can relay, not as an empty error. They are final answers, not transient failures, so report them rather than retrying.

  • FREE has no audited-page allowance. Nothing that loads a page runs on a Free site, and neither does a full-site crawl. A Free account can still read its own state: site_overview answers with the plan, whether the widget is enabled and whether it has ever reported in, the page allowance and this month's visitors — and says in as many words that it has no scan data, rather than returning a score of zero. Managing monitored pages and reading their history needs Starter; the findings themselves need Pro. Saying so honestly is better than implying otherwise.
  • Shopify needs one human step. The widget is a theme app embed, which can only be switched on from the Shopify admin. The account, the domain and the scans all work; the welcome email hands the merchant the App Store link for the last step.
  • Payment is always a handoff. Nothing here charges a card. Upgrading returns a Stripe or Shopify URL for the user to open themselves.
  • Rate limits apply per IP address, per mailbox and per site domain. Five signups an hour from one IP address, three a day for one site domain, and twenty a day for one company email domain, or five a day for one exact address where the domain is a shared provider like gmail.com, since everybody else's gmail signup is none of this caller's business. A call with no site domain to count gets its own bucket instead: ten a day per IP address for registering without a website, and a separate ten for signing in, so spending one does not spend the other. Generous for real use and tight enough to stop a loop.
  • A scan measures the rendered page. It does not observe keyboard behaviour, focus visibility, reflow at 400% zoom or screen-reader order. A claim that a site works for those users needs the checks that load the page, not a score.