Open your API to agents, without giving up your user base.

Every approach that lets an agent reach your product also invites one you never approved. This one hands the agent a verifiable human identity, and leaves the decision about your user base exactly where it is now: with you.


The thing you are actually afraid of

Let agents in, and you wake up to a stranger’s bot holding four hundred free accounts.

That is the real cost of being agent-accessible, and it is a cost of access rather than of this protocol. A bot that can sign up can sign up. The question is whether the credential it signs up with is something you recognise, and whether you can refuse it.

Here, the credential is a real inbox. Sign-ups get an address that was verified here, and an agent presenting it presents a person behind it. That person may already be your customer. When they are not, they get a refusal that says so, in a sentence, naming the one action that would work.

AgentOnboard owns identity. Your application owns permissions. We cannot tell you who is entitled to your product, because we have no record of your accounts, your plans, or your bans. What we hand you is a way to ask.


What you get

A verified human, on the request. Every verified request carries a signed, five-minute, domain-scoped token naming the person it was issued to. You read the email out of it, and you already have a users table.

A check that runs where your data already is. Verification happens in your process, against our public keys. No introspection call, no per-request callback into us, no round trip on the hot path.

A limit on the blast radius, and the last word in your own hands. That is yours to hold. We say so plainly in the docs, so nobody discovers it after you have shipped.


What it costs

The package has one runtime dependency. There is no account to create, no console to sign in to, no dashboard to configure, and no secret to request before you can call the verifier. That is a deliberate choice: the public API and the error messages are the entire integration surface.

@agentonboard/sdk reads the header, checks the signature, and hands your handler your own user id. If you already have an auth layer, use the core on its own and keep your own error handling and response shapes.


The whole integration, in a route handler

One import, one lookup, one guard. The line that touches your database is marked.

app/api/notes/route.ts

import { requireAgentAccount } from "@agentonboard/sdk/next";

export async function POST(request: Request) {
  const session = await requireAgentAccount(request, {
    audience: "notes.com",
    resolveAccount: async ({ email }) => {
      // <- your lookup goes here; normalize the case
      return accounts.get(email.toLowerCase()) ?? null;
    },
  });

  // A finished response: 401, 403 or 500. Nothing to unwrap.
  if (!session.ok) return session.response;

  return Response.json({ created_by: session.user });
}

session.user is your own id, so scoping the next query to the caller is the following line and nothing else has to be threaded through. The verifier never throws: every failure comes back as a code you can branch on, and a human-readable sentence beside it.


And the part that closes the loop

An agent that has never heard of your API will not find it. One that has will not know which hostname to mint a token for, or what a 403 means. auth.md is a plain markdown file at the root of your domain that answers both — the audience string to use, your endpoints, and what to do about each rejection. It is a static file, so publishing it is a copy into public/.

The rejection table is the part worth the bytes. It is the only place an agent is told the difference between minting a new token and sending a human to connect their account — which is exactly what the 401 / 403 / 500 split carries.


Your API, your accounts, one more way in.

Start with the install and the first verification call. Everything else is on the page after it.