Skip to content

Inbound agents: check the agents that call your APIs

Agent authorization governs the agents your organisation runs. Inbound agents covers the other direction: AI agents run by other organisations that call your APIs, MCP servers and data.

An agent enrolled in Shield360 can carry an agent passport in the Shield360-Agent header. A passport is a short-lived signed statement of:

  • which agent is calling,
  • which organisation runs it (the operator) and that organisation’s verified domain,
  • when it acts for a person, that it does, as a pairwise id: stable for that person at your service, unlinkable across services, and never their name or email.

The Shield360 SDK verifier checks the passport where the call arrives. It decides under the policy you set here and reports each check to your project’s inbound decision log.

A passport proves who is calling. It never grants access by itself: your own API keys, OAuth tokens and permissions still decide what the call may do.

Inbound agents is part of the Security module. Open Govern → Agent access → Agent authorization and choose the Inbound agents tab (/agent-authorization?view=inbound). Everything on the tab belongs to the environment you are in: each environment (project database) has its own policy and log.

FieldDefaultMeaning
modeobserveobserve: verify and record every call, and never block one. The log shows what enforce would have denied. enforce: block calls the policy denies; the verifier answers 403.
requireAgentfalseA call without a passport is denied (reason absent) and reported. Off: such calls are allowed and the verifier does not report them.
operators[]The operators you accept, by organisation id or verified domain (for example acme.com). Empty: any verified operator. Others are denied (operator_not_allowed). Up to 100.
requireOnBehalfOffalseThe passport must say the agent acts for a person (on_behalf_of_required).
minAssurancenoneThe least assurance you accept for the person behind the agent (see below). A weaker one, or none when one is required, is denied (assurance_too_low).
onlinefalseAlso ask the issuing Shield360, on every call, whether the passport is still good: the agent’s kill switch, its token, and that the passport was issued. Slower. If the issuer cannot be reached, the call is not denied for that reason.

When an agent acts for a person, the passport says how strongly it ties the agent to that person, strongest first:

LevelMeaning
delegatedThe person, signed in, gave this agent a mandate. The passport names the mandate and when it ends. A mandate the person revoked is denied (mandate_revoked).
namedA member of the agent’s organisation, named by the agent.
unverifiedA person named by the agent who is not a member of its organisation.

minAssurance is none (the default: accept any call, whether or not it names a person), unverified, named or delegated. Each level also accepts the stronger ones.

A passport that is present but invalid (bad signature, expired, wrong audience, replayed and so on) is always denied. It is only blocked in enforce.

Start in observe. Look at the decision log and Operators seen for a while, list the operators you expect, then switch to enforce.

Changing the policy needs Manage agent authorization. Each change is in the audit log with the policy before and after. Verifiers pick a change up within a minute.

Create an API key

In the environment that should receive the decisions, create an API key under Settings → API keys. Keep it in your service’s secrets, for example as SHIELD360_API_KEY.

Add the verifier

Install the Shield360 TypeScript SDK and add the verifier to your API or MCP server. audience is your service’s host name: the one agents ask for a passport to.

import { agentVerifier } from "shield360/agents";
const verifier = agentVerifier({
audience: "api.example.com",
policyFrom: { apiKey: process.env.SHIELD360_API_KEY!, baseUrl: "https://app.shield360.ai" },
report: { apiKey: process.env.SHIELD360_API_KEY!, baseUrl: "https://app.shield360.ai" },
});
// Express / Connect: sets req.shield360Agent and answers 403 when enforce blocks.
app.use(verifier.middleware());
// Or check one request yourself.
const result = await verifier.check(request, { route: "/v1/jobs", method: "GET" });

The Python SDK has the same verifier as shield360.agents.AgentVerifier, with ASGI and WSGI middleware.

Watch the log

Calls appear in the Inbound agents tab. The tab also shows this snippet with your Shield360 address filled in.

  • policyFrom makes the verifier follow the policy you set in the console. It caches the policy for 60 seconds. Leave it out to pass a local policy instead.
  • report sends each result to the inbound decision log in batches. Reporting never blocks or slows a call. A verifier that cannot reach Shield360 keeps checking passports offline.
  • Passports are checked offline against the issuer’s public keys. Only the online check, when you turn it on, calls the issuer for each request.

Both routes take the environment’s API key as Authorization: Bearer <key> and act on that key’s own environment. Nothing in the request can name another. Each key is rate limited.

GET /api/inbound-agents/policy answers the policy:

{ "mode": "observe", "requireAgent": false, "operators": [], "requireOnBehalfOf": false, "online": false, "minAssurance": "none" }

POST /api/inbound-agents/decisions takes up to 100 check results:

{
"decisions": [
{
"status": "verified",
"decision": "allow",
"blocked": false,
"agent": {
"id": "cm1agent",
"name": "Booking agent",
"status": "enrolled",
"environment": "production",
"risk": "high",
"owner": { "domain": "acme.com" },
"reviewedAt": "2026-10-01T09:00:00.000Z",
"enrolledAt": "2026-08-14T09:00:00.000Z"
},
"operator": { "id": "cm1org", "name": "Acme", "domain": "acme.com" },
"onBehalfOf": {
"id": "q3J5aV8tT0RkZ0ZxbFhB",
"domain": "acme.com",
"assurance": "delegated",
"mandate": { "id": "cm1mandate", "expiresAt": "2026-11-28T00:00:00.000Z" }
},
"scopes": ["jobs.read"],
"issuer": "https://app.shield360.ai",
"passportId": "6f1d3c1e-8a7b-4c2d-9e0f-123456789abc",
"expiresAt": "2026-10-28T12:05:00.000Z",
"at": "2026-10-28T12:00:01.000Z",
"route": "/v1/jobs",
"method": "GET",
"receiver": "api.example.com"
}
]
}
FieldRule
statusverified, absent or invalid
decisionallow or deny. An invalid result is always deny
blockedtrue only when decision is deny
reasonOptional reason code, such as expired, operator_not_allowed, assurance_too_low, mandate_revoked, issuer_unreachable or replay_check_failed
agent, operator, onBehalfOfOptional. Ids of up to 128 characters, names of up to 200
onBehalfOf.assuranceOptional: delegated, named or unverified. onBehalfOf.mandate is optional { id, expiresAt }
agent.status, agent.environment, agent.risk, agent.owner.domain, agent.reviewedAt, agent.enrolledAtOptional, and each may be null. status is enrolled or not_enrolled; environment and risk are up to 64 characters; dates are ISO 8601
scopesUp to 32, each [A-Za-z0-9_.:-], up to 64 characters
atWhen the call was checked: within the last 7 days and no more than 5 minutes ahead
route, method, receiverOptional. at, route, method and receiver may also be given once on the body for every result

The answer is { "accepted": <n> }. Malformed JSON, more than 100 results, or any result with a bad field is a 400, and nothing in that batch is recorded. Too many requests for the key is a 429 with Retry-After. A bare array, or { "results": [...] }, is read the same way as { "decisions": [...] }.

The verifier reports every call that carried a passport, and calls without one only when the policy denied them. Calls without a passport that were allowed are not reported, so the log and its counts are not a count of all your traffic.

Each check keeps:

  • the status, decision, whether it was blocked, and the reason code,
  • the agent’s id and name,
  • the operator’s id, name and verified domain,
  • the pairwise id and email domain of the person the agent acted for, the assurance level, and the mandate’s id and end when it was delegated,
  • what the issuer said about the agent: enrolled or not, its environment and risk tier, its owner’s email domain, and when it was enrolled and last reviewed,
  • the scopes the passport presented,
  • the issuer, the passport id (jti) and its expiry,
  • the method, the route’s path, the receiver, and when it was checked.

Never recorded: the passport itself, any request header (including Authorization and cookies), the request or response body, and the route’s query string, which is cut off before anything is stored. The agent’s stated purpose is checked but not kept.

Inbound decisions are kept for 400 days, like the agent authorization decision log, then deleted by the daily job.

The tab shows:

  • the last 30 days’ counts of reported checks: verified, no passport (denied ones only), invalid, denied and blocked,
  • Operators seen in the last 30 days, with how many checks each operator’s agents made, how many were denied, how many named a person at each assurance level, and when they were last seen. An operator not on your allowed list is flagged,
  • the inbound decision log, newest first, filtered by status, decision, assurance, operator, agent and period.

Export CSV and Export JSON download the filtered log, newest first, up to 10,000 records. When more records match, the export says it was cut short: the JSON has "truncated": true and the response carries X-Shield360-Truncated: true. Narrow the period to export the rest. CSV cells that start with =, +, - or @ are defused so a spreadsheet does not run them.

Exporting needs View agent authorization and Generate evidence packages. Each export is in the audit log with its filters, how many records it held and whether it was cut short.

Inbound decisions count toward agent authorization evidence for ISO/IEC 42001:

  • The control check Every AI agent has an owner, a purpose and a current access review (agents.authorized) counts the period’s inbound decisions. It is applicable once an inbound decision has been recorded, even in an environment with no agents of its own. The finding says how many inbound calls were checked and denied, and from how many operators. It also says that decisions are what the verifiers reported, and that a call is only blocked where a verifier runs in enforce.
  • The evidence package carries the period’s inbound counts and the operators seen in agent-authorization.json.
ActionPermissionShipped roles
See the policy, the log and operators seenView agent authorizationAdministrator, Compliance officer, Analyst, Security
Change the policyManage agent authorizationAdministrator, Security
Export the logView agent authorization and Generate evidence packagesAdministrator, Compliance officer

Every route checks these on the server. The page only hides what you cannot do. Auditors and the read-only demo cannot change the policy.