Pupul Identity docs

Two verbs. A scoped, consented identity token, gated by how a person types.

The base URL is https://noctaracorp.com. All endpoints are JSON over HTTPS and CORS-open. There are no SDKs to install for the core flow; it is three HTTP calls. pupulcorp.com is the front door; noctaracorp.com is the issuer that signs, verifies, and revokes.

10 seconds   See a registry check, no key, no signup

One curl. It returns the exact response shape a real check returns, from the same code paths, over a fixture record that says so in its first field. Real records need a key and the person's recorded consent.

curl "https://noctaracorp.com/api/v1/registry-demo"

Four claims come back: is_human, is_continuous, has_deviated, stands_behind, each with the evidence and thresholds behind it. A claim we cannot evidence is null, never false. Full registry docs live at noctaracorp.com/developers.

10 minutes   Zero to first read

Prerequisite: the person you test with has taken the free read at noctaracorp.com/take (four minutes, no signup). Then:

# 1. Mint your credentials (one call, no human in the loop)
curl -X POST https://noctaracorp.com/api/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"app_name":"My App","email":"you@yourapp.com","redirect_uris":["https://yourapp.com/callback"]}'

# 2. Send the person to consent (browser)
https://noctaracorp.com/api/oauth/authorize?client_id=app_...&redirect_uri=https://yourapp.com/callback&scope=word%20rhythm%20force&response_type=code&state=xyz

# 3. Exchange the code, then read the person (server)
curl -X POST https://noctaracorp.com/api/oauth/token -d 'grant_type=authorization_code&code=...&client_id=app_...&client_secret=psk_...&redirect_uri=https://yourapp.com/callback'
curl -X POST https://noctaracorp.com/api/pupul-context -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" -d '{}'

That last call returns the consented read and an AI greeting you can seed straight into your own model's system prompt. Pass {"greet": false} for the raw read only; it is faster and never touches a model.

Honest scope: keystroke presence is a v1 confidence signal, not a calibrated biometric with published false-accept/reject rates. The token always carries an honest presence label. Your app decides how much to trust it.

start here   Sign in with Pupul (OAuth)

The production lane. Standard OAuth 2.0 authorization code, discoverable at /.well-known/oauth-authorization-server. The person consents on our screen with their email and mark; your app receives an ES256 token you can verify offline forever against /.well-known/jwks.json.

1. Mint credentials, self-serve, one call:

curl -X POST https://noctaracorp.com/api/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "app_name": "My AI App",
    "email": "you@yourapp.com",
    "redirect_uris": ["https://yourapp.com/callback"],
    "public": false
  }'
-> { "ok": true, "client_id": "app_...", "client_secret": "psk_..." }

The secret is shown once and stored only as a hash. Set "public": true for a browser-only app (no secret; PKCE S256 required). Rotate any time with { "action": "rotate", "client_id", "client_secret" } to the same endpoint.

2. Send the person to consent at /api/oauth/authorize?client_id&redirect_uri&scope=word rhythm force&response_type=code&state=..., then exchange the code server-side at /api/oauth/token. Or skip the plumbing in a browser app:

<script src="https://noctaracorp.com/sdk.js"></script>
Pupul.signIn({ clientId: "app_...", redirectUri: location.origin + "/callback" });
// on /callback:
const tok = await Pupul.handleCallback({ clientId: "app_...", redirectUri: location.origin + "/callback" });

3. One call turns the sign-in into understanding. The Context API returns the consented read plus a greeting already personalized by an AI, ready to seed your own model's system prompt:

POST /api/pupul-context   Authorization: Bearer <access_token>
-> { "ok": true, "word": "...", "rhythm": "...", "force": "...", "ai_message": "..." }

Meter and price: every consented read is metered. Check your usage with your own credentials at GET /api/oauth/usage (Basic auth). The first 1,000 reads each month are free per app. Past that the endpoint returns 402 free_tier_exhausted with an upgrade_url. Subscribe to the Developer plan, 49 dollars a month, which includes 25,000 reads, at buy.stripe.com/eVqcN52Ee4kNeSDe2x8AE03 and enter your app's client_id at checkout; your cap lifts within seconds of payment, no email, no waiting. Reads beyond the free 1,000, and beyond the plan's 25,000, are 5 cents a read (0.05 dollars), arranged with us before they bill, never charged by surprise. That is the whole price list for this API: 1,000 free reads a month per app, 49 a month for 25,000, 0.05 a read after that. Two behaviors worth knowing: {"greet": false} skips the AI greeting for a faster, cheaper raw read, and under extreme global load the greeting may return ai_message_capped: true while the read itself always flows.

What is never in a token, at any price: the half-truth, the compliance lever, anything about how a person can be moved. Scopes are hard-capped to word rhythm force. Every grant is revocable by the person at noctaracorp.com/identity, and revocation kills the token everywhere.

The model

A person has a read on file (a word and a rhythm, taken once at noctaracorp.com). They enroll a keystroke signature. When your app needs their identity, they authorize a scoped grant by typing their phrase; you receive a token. You verify the token to read the consented claims and the presence label.

enroll   Enroll a keystroke signature

POST /api/identity-enroll

{
  "email":   "person@example.com",
  "phrase":  "their word or short phrase",
  "samples": [ [ {"k":"a","d":0,"u":92}, ... ], ...x4 ]
}

Each sample is the keystroke timing of one typed attempt: per key, d = keydown ms and u = keyup ms, both relative to the first keydown. Send at least 3 (4 is better). We store only a derived reference, never the phrase. Returns { ok, enrolled, self_min }.

read + verify   Request a scoped, presence-gated grant

POST /api/identity-grant

{
  "email":   "person@example.com",
  "app_id":  "your.app",
  "scope":   "word rhythm force",
  "phrase":  "their phrase",
  "kd":      [ {"k":"a","d":0,"u":90}, ... ],
  "require_presence": true
}

Allowed scopes: word, rhythm, force. The person consents to exactly these, and nothing deeper is grantable: the half-truth and the compliance lever never leave the person's own room. With require_presence: true, the grant returns 401 unless the keystroke rhythm verifies. Returns a signed token plus the presence result. One difference worth knowing: this grant lane signs HS256 on a secret only we hold, so its tokens are verified by calling /api/identity-verify. The OAuth lane above signs ES256 and is the one you can verify offline against the JWKS. If you want offline verification, use OAuth.

{
  "ok": true,
  "token": "<jwt>",
  "scope": "word rhythm force",
  "presence": { "level": "verified", "confidence": 0.77 },
  "expires_at": "..."
}
presence.levelmeaning
verifiedkeystroke rhythm matched (confidence ≥ 0.65). Token amr includes keystroke.
weakpartial match (0.5 to 0.65). Treat with caution.
faileddid not match. With require_presence, the grant is refused.
unverifiedno keystroke sample sent, or no enrollment on file. Email-only trust.

verify   Verify a token you received

POST /api/identity-verify with { token }, or Authorization: Bearer <token>. This is the verifier for the HS256 grant lane. OAuth access tokens are ES256 and need no call to us at all: fetch /.well-known/jwks.json once and verify them yourself, forever. Returns the consented claims and presence:

{
  "ok": true, "valid": true,
  "claims": {
    "sub": "<mark>", "aud": "your.app", "scope": "word rhythm force",
    "amr": ["email","keystroke"], "presence": "verified", "presence_conf": 0.77,
    "word": "...", "rhythm": "...", "force": "...", "exp": 0
  }
}

Revoked or expired tokens return 401. A person can revoke any grant from their identity page; verification fails immediately after.

Why presence matters

Email and passwords prove what a person knows. As agents act on people's behalf, the question becomes whether a real, specific human is present at the moment of consent. The presence label is an honest answer to that, bound into the token, so a relying party can require it for high-stakes actions and skip it for low-stakes ones.

No key request needed for the OAuth rail: POST /api/oauth/register mints your credentials on the spot. Building something unusual and want the founder in the loop? Ask here.