PermDock
Standards

OpenAPI 3.2

How PermDock emits OpenAPI 3.2 security schemes, per-operation security and x-permdock-permissions, with registered x-oai-* and x-permdock-* fallbacks for 3.1 documents, and imports OpenAPI documents into a catalog.

Status: planned Phase: 2 Adapter phases: permdock/openapi 2, permdock openapi (CLI) 2, permdock/hono and permdock/server hooks 1, other HTTP and RPC adapter hooks 2.

What it is

OpenAPI 3.2 was released in September 2025. The changes relevant to authorization are:

  • Device authorization flow as a first-class oauth2 flow, next to the existing authorization code, client credentials and implicit flows.
  • oauth2MetadataUrl on an oauth2 security scheme, pointing at the authorization server's metadata document so clients discover endpoints instead of hard-coding them.
  • deprecated on security schemes, so an API can phase out an API key or a legacy flow while it still works.
  • Security schemes referenced by URI, so one organisation-wide security scheme document can be shared across many API descriptions.

Everything else PermDock relies on already existed in 3.1: components.securitySchemes (apiKey, http, oauth2 with flows.*.scopes, openIdConnect, mutualTLS), a root-level security default, per-operation security overrides where an empty array means public, OR across array entries and AND within an object, and x-* extensions on any object. For three of the new features the OpenAPI Initiative's Extension Registry lists a pre-3.2 extension: x-oai-deprecated, x-oai-deviceAuthorization and x-oai-deviceAuthorizationUrl. There is no registered extension for oauth2MetadataUrl or for URI-referenced schemes; see OpenAPI registries.

Why it matters for PermDock

An OpenAPI document is the one artefact that gateways, SDK generators, API portals and agents all read. If a route is guarded with protect(permissions.post.delete, ...), that requirement should be visible in the document as an OAuth scope, not only in application code. The reverse direction matters too: a team that already has an OpenAPI document with scopes has a permission catalog in disguise, and PermDock should be able to import it.

OpenAPI 3.2 specifically matters for agent callers. MCP clients and A2A agents obtain tokens through OAuth; oauth2MetadataUrl tells them where, the device flow covers headless agents that cannot open a browser, and deprecated lets an API retire a scheme without breaking a fleet of agents overnight. See ADR 0014.

How PermDock uses it

Output

permdock/openapi and the CLI openapi command derive the security parts of a document from the permission definition and the routes that use protect:

  • components.securitySchemes.<name>.flows.<flow>.scopes is filled from permission.scope for every permission referenced by a route (post:delete, billing:invoice:pay). The scheme name, flow types, oauth2MetadataUrl, deviceAuthorization and deprecated come from the adapter configuration.
  • Each operation guarded by protect receives security: [{ <scheme>: ['post:delete'] }]. Routes with no guard get no entry (they inherit the document default), and routes guarded by an explicitly public permission get security: [].
  • Each guarded operation also receives x-permdock-permissions: ['post.delete'], the permission key list, so tools that do not understand scopes can still see the requirement and permdock usage can cross-check the document against the catalog.
  • Resource schemas are exported via Standard JSON Schema into components.schemas when the validator supports it (see Standard Schema).

Framework hooks: hono-openapi describeRoute, @hono/zod-openapi createRoute, oRPC oo.spec(middleware, ...), trpc-to-openapi protect, @elysia/openapi detail, @fastify/swagger schemas and @nestjs/swagger decorators all accept a security fragment; the HTTP adapters and RPC adapters pass PermDock's fragment into them. Producers without such a hook (next-openapi-gen for Next.js and the other frameworks it scans, hand-written descriptions) receive the same fields as an Overlay applied at build time. Downstream, SDK generators (Hey API, Orval), docs UIs (Scalar) and gateways read the standard security fields and need nothing PermDock-specific; the pipeline and the tools in it are on the OpenAPI ecosystem page and the rule is ADR 0023.

import { createPermDock } from 'permdock/hono'
export const { permdock, protect, openapi } = createPermDock(policy, {
  subject: (c) => c.get('user'),
  openapi: {
    target: '3.2',                      // '3.1' switches to the registered x-oai-* and x-permdock-* fallbacks
    scheme: 'oauth',
    oauth2MetadataUrl: 'https://auth.example.com/.well-known/oauth-authorization-server',
    flows: ['authorizationCode', 'deviceAuthorization'],
  },
})
app.delete('/posts/:id', protect(permissions.post.delete, (c) => loadPost(c.req.param('id'))), handler)
// operation: security: [{ oauth: ['post:delete'] }], x-permdock-permissions: ['post.delete']

3.1 fallbacks

When target: '3.1' is selected the emitter writes the same information using extensions, choosing a registered name whenever one exists:

3.2 field3.1 fallbackRegistered in the OAI Extension Registry
flows.deviceAuthorizationx-oai-deviceAuthorization inside flowsyes
deviceAuthorizationUrl inside that flowx-oai-deviceAuthorizationUrlyes
deprecated on a security schemex-oai-deprecatedyes
oauth2MetadataUrlx-permdock-oauth2MetadataUrlno x-oai-* extension is registered for it
Scheme referenced by URIInlined into components.securitySchemesnot applicable

Only the three registered x-oai-* names are ever emitted; PermDock does not coin x-oai-* names of its own, because oai is the namespace reserved for the OpenAPI Initiative (Namespace Registry). Anything else PermDock needs to say lives under x-permdock-* (OpenAPI registries, ADR 0019). The scope and x-permdock-permissions output is identical in both versions. The importer reads both the native 3.2 fields and every fallback in the table.

3.3 and later

target: '3.3' is experimental and tracks the v3.3-dev branch; it currently emits the 3.2 shape plus x-permdock-securityProfile and is documented on OpenAPI 3.3. The default target stays 3.2 until 3.3 is released.

Import

permdock openapi import reads a 3.1 or 3.2 document and emits src/permissions.generated.ts, a deterministic definePermissions() call with a // @generated header. Each securitySchemes.*.scopes entry becomes a permission whose scope is the OAuth scope; the key is derived from the scope by replacing : with .; an operation's security becomes a hint that lands in the permission's meta so permdock usage can report scopes that no route requires. The generated file merges with hand-written definitions like any other feature file (see larger apps).

Mapping table

OpenAPI 3.2 conceptPermDock concept
securitySchemes.<name>.flows.*.scopespermission.scope for every permission used by a guarded route
Per-operation securityprotect(permission, ...) on that route
security: []Route guarded by a permission that every subject is granted (public)
oauth2MetadataUrlAdapter option; passed through to the document (or x-permdock-oauth2MetadataUrl for 3.1)
Device authorization flowAdapter option flows: ['deviceAuthorization'] (or x-oai-deviceAuthorization with x-oai-deviceAuthorizationUrl)
deprecated on a schemeAdapter option per scheme (or x-oai-deprecated)
Scheme referenced by URIAdapter option schemeRef; inlined for 3.1
x-permdock-permissionsPermission key list for the operation
components.schemasStandard JSON Schema export of resource schemas
Imported scopespermissions.generated.ts leaves with scope set

Sources

Open questions

  • The exact shape of the openapi adapter options; the option names in the example above (scheme, flows, schemeRef) are illustrative and will be fixed when the permdock/openapi entry is implemented. target mirrors the CLI --target flag.
  • Whether a permission with a portable condition should be represented in the document at all (a scope cannot express authorId = subject.id); the current plan emits the scope and leaves the condition to x-permdock-permissions consumers.
  • Whether import should also read x-permdock-permissions to round-trip keys exactly, or only derive keys from scopes.
  • Validating emitted documents against the official 3.2 JSON Schema in CI, and whether permdock doctor should do the same locally.

On this page