Get started

Reference

API reference

The hosted Identity API, and every option the middleware takes.

Base URL https://api.wayleave.dev · Auth Authorization: Bearer <key>, keys from your dashboard. Requests and responses are JSON. Every response carries a request_id.

POST /v1/identify

Classify one request from its headers. Stateless: Wayleave never fetches the URL you name. It is a string to be matched against, not a thing to go and get.

Body

FieldTypeNotes
urlstring, requiredthe full URL the request was made to
headersobject, requiredthe request’s headers as received; names are case-insensitive
methodstringdefaults to GET
ipstringclient IP, if you trust your proxy to supply it

Response 200

{
  "lane": "verified_agent",
  "verified": true,
  "identity": {
    "operator": "Example Labs",
    "agentId": "…",
    "keyid": "…",
    "source": "https://…/http-message-signatures-directory"
  },
  "signature": { "present": true, "valid": true, "reason": null,
                 "expires": "2026-09-28T…", "nonce_fresh": true },
  "reasons": ["crypto: valid signature"],
  "request_id": "…",
  "latency_ms": 0.8
}

lane is one of verified_agent, declared_agent, suspected_bot, human. verified is true only for a valid Ed25519 HTTP Message Signature (RFC 9421, Web Bot Auth). A replayed single-use signature comes back verified: false with nonce_fresh: false. identity is null unless verified — an unverified identity is a claim, and this API does not repeat claims as facts.

Errors

StatusMeaning
400body missing url or headers, or not valid JSON
401missing or rejected API key; answers WWW-Authenticate: Bearer
413body larger than 64 KB
429over your per-key rate limit; Retry-After in seconds

Every 200 carries x-ratelimit-remaining.

POST /v1/identify/batch

The same, for 1–100 requests at once. Each item counts against your rate limit.

{ "requests": [ { "url": "…", "headers": { … } }, … ] }

Response: { "results": [ …one per request, in order… ], "count": n, "request_id": "…" }

Webhooks — first-seen identity

When configured for your key, Wayleave POSTs the first time a verified identity is seen:

{ "type": "identity.first_seen", "at": "…", "identity": { … }, "lane": "…" }

Headers: x-wayleave-timestamp, and

x-wayleave-signature: v1=<hex HMAC-SHA256 of "<timestamp>.<body>" with your webhook secret>

Reject signatures older than five minutes, or a captured delivery can be replayed at leisure. HTTPS endpoints only.

GET /healthz

{ "ok": true } — no auth.

Security model

Keys are stored as SHA-256 hashes and compared in constant time. The API never fetches the URLs it is asked about, so it cannot be turned into a request forwarder. Responses carry nosniff, X-Frame-Options: DENY, HSTS and no-store.

Report a vulnerability to gibran@wayleave.dev. Please give us a way to reach you back.

Middleware reference — wayleave on npm

new Wayleave(options)
OptionTypeMeaning
pricedPaths{ [prefix]: usd }HTTP 402 for non-human lanes under these prefixes
strictPricedPathsbooleanonly confirmHuman callers pass a priced route free
confirmHuman(req) => booleanyour own signal that a person is present, usually a session
verifyPaymentverifiere.g. coinbaseFacilitator({…}) from wayleave/x402; absent means priced routes stay at 402
rules{ [lane]: [[prefix, allow], …] }first match wins
rateLimits{ [lane]: perMinute }fixed window
directoriesdirectories or resolverwhich signing keys to trust
policy{ url, publicKey }a signed policy document fetched and verified at runtime
meter{ apiKey }send crossings to your dashboard
store, sinkinterfacesswap replay and rate storage, or the event sink
onWarn(message) => voidwhere startup warnings go; defaults to console.warn

gate.express() returns Express middleware. gate.handleAsync(req) returns { status, lane, why } for any other framework; gate.handle(req) is its synchronous twin and cannot evaluate a remote policy or await a network payment verifier. Full types ship in index.d.ts.