{"openapi":"3.1.0","info":{"title":"NoForm auth.md protocol","version":"1.0.0","description":"Agent-native signup. Every app is a resource at /a/{slug} (or its custom-domain root; noform.dev's own root serves the self-app). Agents discover the contract via /auth.md and the .well-known metadata, then run the register → claim → complete ceremony to provision an email-verified user. The app's backend validates credentials via introspect.","contact":{"email":"support@noform.dev","url":"https://noform.dev/docs"}},"servers":[{"url":"https://noform.dev"}],"components":{"securitySchemes":{"appApiKey":{"type":"http","scheme":"bearer","description":"The app's nf_live_ API key (introspect only)."},"verifiedJwt":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"A JWT signed by an issuer the app trusts (iss, aud = app resource, sub, exp, single-use jti)."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Machine-readable error code"},"detail":{"type":"string"}},"required":["error"]},"Registration":{"type":"object","properties":{"credential":{"type":"string","description":"nf_live_… (anonymous mode only)"},"claim_token":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"expires_at":{"type":"integer","description":"Epoch ms"},"login_url":{"type":"string","description":"Pre-filled post-signup link when the app configures one"}},"required":["claim_token","expires_at"]},"Introspection":{"type":"object","properties":{"active":{"type":"boolean"},"email":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"sub":{"type":"string"},"expires_at":{"type":"integer"},"agent_platform":{"type":"string"}},"required":["active"]}},"parameters":{"slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The app's slug (its resource is /a/{slug})."}}},"paths":{"/a/{slug}/auth.md":{"get":{"operationId":"getAuthMd","summary":"The human/agent-readable protocol contract (markdown)","parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"text/markdown contract"},"404":{"description":"Unknown app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/.well-known/oauth-protected-resource":{"get":{"operationId":"getProtectedResourceMetadata","summary":"RFC 9728 protected-resource metadata","parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"Resource metadata JSON"},"404":{"description":"Unknown app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/.well-known/oauth-authorization-server":{"get":{"operationId":"getAuthServerMetadata","summary":"AS metadata + agent_auth endpoint block","parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"AS metadata JSON"},"404":{"description":"Unknown app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/agent/auth":{"post":{"operationId":"register","summary":"Step 1 — register (anonymous or email-required)","parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"agent_platform":{"type":"string"}}}}}},"responses":{"201":{"description":"Registration created","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Per-IP registrations allowed per hour"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Registration"}}}},"400":{"description":"Disposable email / bad body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-IP / per-tenant rate limit or monthly cap (Retry-After header set)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/agent/auth/claim":{"post":{"operationId":"claim","summary":"Step 2 — claim for a human email (sends 6-digit code)","parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"claim_token":{"type":"string"},"email":{"type":"string","format":"email"}},"required":["claim_token","email"]}}}},"responses":{"200":{"description":"{ ok: true } — code emailed"},"400":{"description":"Disposable email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Cross-app claim_token (scope_mismatch)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown claim","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Claim not in preclaim state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-email (3/h) or per-IP code-send limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/agent/auth/claim/complete":{"post":{"operationId":"completeClaim","summary":"Step 3 — verify the one-time code","parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"claim_token":{"type":"string"},"otp":{"type":"string"}},"required":["claim_token","otp"]}}}},"responses":{"200":{"description":"{ status: \"active\", scopes }"},"400":{"description":"Invalid / expired / locked OTP","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Cross-app claim_token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown claim","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/agent/auth/revoke":{"post":{"operationId":"revoke","summary":"Revoke a credential","parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"credential":{"type":"string"}},"required":["credential"]}}}},"responses":{"200":{"description":"{ ok: true }"},"404":{"description":"Unknown credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/agent/auth/introspect":{"post":{"operationId":"introspect","summary":"Validate a credential (the app backend's one call)","security":[{"appApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"credential":{"type":"string"}},"required":["credential"]}}}},"responses":{"200":{"description":"Introspection result (active=false for unknown/expired/revoked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Introspection"}}}},"401":{"description":"Missing/invalid API key. Carries WWW-Authenticate: Bearer resource_metadata=\"…/.well-known/oauth-protected-resource\"."}}}},"/a/{slug}/agent/auth/verified":{"post":{"operationId":"registerVerified","summary":"Agent-verified (ID-JAG) provisioning — Builder+ apps","security":[{"verifiedJwt":[]}],"parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"201":{"description":"Active account provisioned (credential returned)"},"401":{"description":"Missing/invalid bearer (WWW-Authenticate set)"},"403":{"description":"Issuer not trusted by this app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Replayed jti — each JWT is single-use","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a/{slug}/agent/auth/status":{"get":{"operationId":"status","summary":"Flow status for the initiating browser (keyed by claim_token)","parameters":[{"$ref":"#/components/parameters/slug"},{"name":"claim_token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ found, status, bridged, events }"}}}}}}