api reference

auth-staging.notme.bot

signet identity authority API. bridge certificates via OIDC token exchange, GHA automation, and agent registration.

base https://auth-staging.notme.bot

certificate exchange

POST /cert
Generalized cert exchange. Present any proof, get a scoped bridge cert. This is the core signet protocol endpoint.
request
{ "proof": { "type": "session" }, "scopes": ["bridgeCert"] } // or with OIDC: { "proof": { "type": "oidc", "token": "<JWT from any issuer>" } } // or bootstrap (deployer only): { "proof": { "type": "bootstrap", "code": "abc12345" } }
response
{ "certificates": { "mtls": "-----BEGIN CERTIFICATE-----\n...", "signing": "-----BEGIN CERTIFICATE-----\n..." }, "identity": "wimse://notme.bot/principal/a1b2c3d4-...", "scopes": ["bridgeCert"], "expires_at": 1711670400, "binding": "9f86d081...", "authority": "https://auth-staging.notme.bot", "principal_id": "a1b2c3d4-...", "auth_method": "passkey" }
notme never sends a private key. The caller generates both keypairs locally with extractable: false and proves possession; only certificates come back. Earlier revisions of this page showed a private_key field — no endpoint has ever returned one.
Proof types: session (passkey cookie), oidc (any JWT), bootstrap (deployer code). Scopes are intersected with the principal's capability grants.
POST /cert/gha
Exchange a GitHub Actions OIDC token for a 5-minute bridge certificate. No stored secrets needed — the OIDC JWT is the credential. Edge-handled (no VPC roundtrip).
authorization

Bearer <GHA OIDC token> with audience notme.bot

request
curl -X POST https://auth-staging.notme.bot/cert/gha \ -H "Authorization: Bearer ${ACTIONS_ID_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"public_keys":{"mtls":"<P-256 SPKI PEM>","signing":"<Ed25519 SPKI PEM>"}, "proofs":{"mtls":"<base64>","signing":"<base64>"}}'
The OIDC token goes in the Authorization header, not in the body. Each proof is a signature by the corresponding private key over the binding PRE-IMAGE mtls_spki ‖ signing_spki ‖ SHA-256(oidc_jwt) — the raw bytes, never their digest.
response
{ "certificates": { "mtls": "-----BEGIN CERTIFICATE-----\n...", "signing": "-----BEGIN CERTIFICATE-----\n..." }, "identity": "wimse://notme.bot/principal/repo%3Aagentic-research%2Fsignet%3Aref%3Arefs%2Fheads%2Fmain", "scopes": ["bridgeCert"], "expires_at": 1711670400, "claims": { "repository": "agentic-research/signet", "ref": "refs/heads/main", "sha": "abc123...", "actor": "github-actions", "workflow": "ci.yml", "run_id": "12345", "event_name": "push" } }
No private key crosses the wire here either. The workflow generates both keypairs in its own runner and sends only the public halves; the response is certificates.
Owners are allowed by GHA_ALLOWED_OWNERS, which has no default — an authority that has not declared whose workflows it trusts trusts none. The token is spent on first use (jti replay protection) and exchanges are rate limited per repository.
The identity names the stable subject — the OIDC sub, which is also the certificate CN — rather than the attestation mechanism. Owner and repository remain readable from sub and from the returned claims.
POST /cert/passkey
The passkey analogue of /cert/gha: an authenticated browser session plus proof of possession, in exchange for a bridge cert pair. Accepts any valid session — passkey, invite, or OIDC — and derives the certificate's identity and auth method from the session, so a certificate never claims an authenticator the session did not use.
Session scopes are INTERSECTED with an allowlist rather than passed through. A deployer session carries authorityManage and certMint; a certificate that silently inherited them would turn a cookie into a minting credential with none of the properties that made the cookie acceptable.

tokens

POST /token
OAuth 2.0 token endpoint. Issues EdDSA-signed access tokens bound to a DPoP proof (RFC 9449), verifiable against the published JWKS.
authorization

A session cookie, plus a DPoP proof header signed by the client's own P-256 key. Tokens are sender-constrained: a stolen token is useless without that key.

GET /authorize
Human authorization grant. A browser flow that asks a present human to approve a scoped delegation; the companion POST /authorize/code, POST /authorize/redeem and POST /authorize/token complete it.

discovery

GET /.well-known/signet-authority.json
Authority discovery document. Lists all endpoints, supported algorithms, and grant types. Analogous to OpenID Connect's /.well-known/openid-configuration, but signet consumes OIDC — it does not issue tokens.
response
{ "issuer": "https://auth-staging.notme.bot", "cert_endpoint": "https://auth-staging.notme.bot/cert", "cert_gha_endpoint": "https://auth-staging.notme.bot/cert/gha", "ca_bundle_endpoint": "https://auth-staging.notme.bot/.well-known/ca-bundle.pem", "algorithms_supported": ["Ed25519"], "grant_types_supported": [ "github_actions_oidc", "dpop" ], "cert_types_supported": ["bridge_certificate"], "token_endpoint": "https://auth-staging.notme.bot/token", "jwks_uri": "https://auth-staging.notme.bot/.well-known/jwks.json", "dpop_signing_alg_values_supported": ["ES256"], "documentation": "https://notme.bot/architecture" }
GET /.well-known/oauth-authorization-server
OAuth 2.0 Authorization Server Metadata (RFC 8414). Also served at /.well-known/openid-configuration — same body, and ONLY because many client libraries probe exclusively there. notme is not an OpenID Provider: it issues no id_token, honours no scope=openid, and publishes an empty response_types_supported.
It does publish id_token_signing_alg_values_supported: ["EdDSA"]. That is a correctness requirement, not a capability claim: a client whose discovery is silent on algorithms defaults to RS256 alone and rejects every token this issuer signs.
GET /.well-known/jwks.json
The authority's token-signing public key, as a JWK set: {kty:"OKP", crv:"Ed25519", alg:"EdDSA"}. "EdDSA" is the JWA algorithm name (RFC 8037 §3.1); "Ed25519" is the curve.
GET /.well-known/epochs.json
Revocation epochs. A certificate names the epoch it was issued under; raising the epoch invalidates everything issued beneath it without waiting for expiry.
GET /.well-known/ca-bundle.pem
CA trust anchor — self-signed X.509 CA certificate (Ed25519). Upload to CF mTLS trust store or use with any X.509 verifier to validate bridge certificates. Served from the SigningAuthority Durable Object (edge-fast, zero VPC roundtrip).
response
-----BEGIN CERTIFICATE----- MIIBFjCByaADAgEC... -----END CERTIFICATE-----
Cache-Control: 1 hour. CA:TRUE, pathLenConstraint:1, KeyUsage: keyCertSign|cRLSign. The key is generated inside Cloudflare and never leaves it — there is no PEM file to exfiltrate.
The cRLSign bit is set and no CRL is published. There is no CRL and no CRL distribution point; revocation works by epoch (see /.well-known/epochs.json) and by short certificate lifetimes. Do not build a verifier that waits for a revocation list — none is coming.
GET /
Content-negotiated landing. Returns HTML (browser) or JSON authority metadata (Accept: application/json).

health

GET /health
Liveness probe, also served at /healthz. Answered at the edge — no auth, no Durable Object, no upstream. Replied to before host canonicalization, so probes from a bare IP or from inside a cluster get an answer rather than a redirect.
GET /.well-known/version
Which BUILD is serving this request. Deploys are manual, so the git tree does not settle what is running; this is the only positive evidence that a fix shipped. Unauthenticated and uncached — a cached answer would defeat the purpose on the request after a deploy.

authentication model

signet is not an identity provider — it is an identity attester. You own your key. signet signs a short-lived certificate binding your public key to your verified identity.

Two grant types are advertised, and they are the only two implemented:

github_actions_oidcCI workflow → POST /cert/gha with a GitHub OIDC JWT (no stored secret)
dpopSender-constrained access token → POST /token with a DPoP proof (RFC 9449)

Everything else that gets you in is an authentication method, not a grant, and the two are deliberately not conflated: passkey, invite, and oidc:github establish a session; a grant issues a token. An earlier revision of this page advertised oidc_token_exchange and github_pat as grant types. Neither was ever implemented — the discovery document now derives its list from a single constant in the code, so a published capability claim and the code cannot drift apart again.

No endpoint returns a private key. The caller generates its keypairs locally, ideally non-extractable, and proves possession of them; only certificates come back. That is the property the whole design exists to provide, and it holds on every route above.

All certificates are Ed25519-signed X.509 with custom OID extensions for subject identity and issuance time, matching the Go authority format.