OpenAPI 3.2 and 3.3
How PermDock emits OpenAPI 3.2 security schemes, per-operation security and x-permdock-permissions (with registered fallbacks for 3.1), imports documents into a catalog, and builds the pinned OpenAPI 3.3 Security Profile draft next to the stable x-permdock-securityProfile twin.
Draft posture: build for 3.3 (pinned to OAI Discussion #5304 expanded design notes, September 2026; v3.3-dev at the commit recorded in x-permdock-catalog.drafts.oas). OpenAPI 3.2 is released and is the default target.
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. In reverse, a document that already has scopes is a permission catalog in disguise, and PermDock imports it. The adapter API and toolchain recipes are on the OpenAPI adapter; flags are on CLI: openapi.
OpenAPI 3.2
OpenAPI 3.2 adds four things relevant to authorization, and each matters to agent callers:
- The device authorization flow as a first-class
oauth2flow, for headless agents that cannot open a browser. oauth2MetadataUrlon anoauth2scheme, so clients discover the authorization server instead of hard-coding it.deprecatedon security schemes, so an API can retire an API key or legacy flow without breaking a fleet of agents overnight.- Security schemes referenced by URI, so one organisation-wide scheme document can be shared across descriptions.
Everything else PermDock uses already existed in 3.1: components.securitySchemes, a root security default, per-operation overrides where an empty array means public, OR across array entries and AND within an object, and x-* extensions anywhere.
Output
components.securitySchemes.<name>.flows.<flow>.scopesis filled frompermission.scopefor every permission a route references. Scheme name, flows,oauth2MetadataUrl,deviceAuthorizationanddeprecatedcome from configuration.- Each operation guarded by
protectreceivessecurity: [{ <scheme>: ['post:delete'] }]. Unguarded routes get no entry and inherit the default; routes guarded by an explicitly public permission getsecurity: []. - Each guarded operation also receives
x-permdock-permissions: ['post.delete'], so tools that do not understand scopes still see the requirement andpermdock usagecan cross-check the document against the catalog. A scope cannot express a condition such asauthorId = principal.id, so a permission with a portable condition also writes it underx-permdock-conditions(OpenAPI registries). - Resource schemas are exported through Standard JSON Schema into
components.schemaswhen the validator supports it (Standard Schema).
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
With target: '3.1' the emitter writes the same information through extensions, using a registered name whenever one exists:
| 3.2 field | 3.1 fallback | Registered in the OAI Extension Registry |
|---|---|---|
flows.deviceAuthorization | x-oai-deviceAuthorization inside flows | yes |
deviceAuthorizationUrl inside that flow | x-oai-deviceAuthorizationUrl | yes |
deprecated on a security scheme | x-oai-deprecated | yes |
oauth2MetadataUrl | x-permdock-oauth2MetadataUrl | no x-oai-* extension is registered for it |
| Scheme referenced by URI | Inlined into components.securitySchemes | not applicable |
PermDock never coins an x-oai-* name, because oai is reserved for the OpenAPI Initiative; everything else lives under x-permdock-* (OpenAPI registries). Scope and x-permdock-permissions output is identical in both versions, and the importer reads the native fields and every fallback.
Import
permdock openapi import reads a 3.1 or 3.2 document (validated against the official JSON Schema first) and writes a deterministic definePermissions() file with a // @generated header. Each scope becomes a permission whose key replaces : with ., so its scope is the OAuth scope again; the operations each action came from land in meta.inferredFrom. When an operation carries x-permdock-permissions, those keys are used as written so they round-trip exactly, and --annotate writes the inferred keys back onto the document (permdock openapi). The file merges with hand-written definitions like any feature file (larger apps).
Mapping table
| OpenAPI concept | PermDock concept |
|---|---|
securitySchemes.<name>.flows.*.scopes | permission.scope for every permission used by a guarded route |
Per-operation security | protect(permission, ...) on that route |
security: [] | Route guarded by a permission every subject is granted: an unconditional allow to anyone() and no deny |
oauth2MetadataUrl | Adapter option (or x-permdock-oauth2MetadataUrl for 3.1) |
| Device authorization flow | Adapter option flows: ['deviceAuthorization'] (or the x-oai-* pair for 3.1) |
deprecated on a scheme | Adapter option scheme.deprecated (or x-oai-deprecated for 3.1) |
| Scheme referenced by URI | Adapter option schemeRef; inlined for 3.1 |
x-permdock-permissions | Permission key list for the operation |
components.schemas | Standard JSON Schema export of resource schemas |
Imported scopes | Generated leaves with scope set |
OpenAPI 3.3
OpenAPI 3.3 is the security-focused next minor, declared strictly compatible with 3.1 and 3.2. As of the pin, the v3.3-dev text is still the 3.2 specification with a new version line, and the v3.3.0 milestone has no target date. The Initiative is investigating the FAPI 2.0 Security Profile and GNAP, and Standardized API Features (features defined by an external specification, with cookies as the example).
A native Security Profile gives a document a standard place to say "this API's OAuth deployment follows FAPI 2.0", so a client can discover that tokens must be sender-constrained and query-parameter tokens are refused, which is what PermDock's FAPI 2.0 alignment enforces on the server. It is the construct agents will read first, which is why PermDock builds the draft with a stable twin instead of waiting. Adoption is additive: everything emitted for 3.2 stays present, and the profile construct appears only with --target 3.3.
The pinned draft
Discussion #5304 has three parts (field names are the proposal's and may change; that is what the pin is for):
- A profile security scheme:
type: profileundercomponents.securitySchemes, withprofileMetadata.name(a registrable profile name),supportedParametersSchema(a JSON Schema of the parameters a requirement may carry), optionalsupportedOperations(a description of the authorization server's operations) andservers(where the profile's metadata is hosted). - Security Profile Requirements: named entries under
components.securityProfileRequirementsthat reference a profile scheme and state, in the profile's vocabulary, the token-endpoint authentication methods, grant types and scopes an operation accepts. - A registry of profile names, so
fapi-20-security-profilemeans one thing everywhere. PermDock maps its short identifiers onto registered names and never invents one.
What --target 3.3 emits
permdock openapi emit --target 3.3 --profile fapi2, or the adapter with target: '3.3', securityProfile: 'fapi2', produces:
{
"openapi": "3.3.0",
"paths": {
"/posts/{id}": {
"delete": {
"operationId": "deletePost",
"x-permdock-permissions": ["post.delete"],
"x-permdock-securityProfile": "fapi2",
"security": [{ "permdockOAuth": ["post:delete"] }]
}
}
},
"components": {
"securitySchemes": {
"permdockOAuth": {
"type": "oauth2",
"oauth2MetadataUrl": "https://auth.example.com/.well-known/oauth-authorization-server",
"x-permdock-securityProfile": "fapi2",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://auth.example.com/authorize",
"tokenUrl": "https://auth.example.com/token",
"scopes": { "post:delete": "Delete a post you own" }
}
}
},
"permdockFapi2": {
"type": "profile",
"profileMetadata": {
"name": "fapi-20-security-profile",
"supportedParametersSchema": "https://permdock.com/schemas/security-profiles/fapi2.json",
"servers": [
{
"name": "default",
"url": "https://auth.example.com/.well-known/oauth-authorization-server"
}
]
}
}
},
"securityProfileRequirements": {
"permdockFapi2PostDelete": {
"securityScheme": {
"$ref": "#/components/securitySchemes/permdockFapi2"
},
"token_endpoint_auth_methods": ["private_key_jwt", "tls_client_auth"],
"grant_types": ["authorization_code"],
"scopes": ["post:delete"]
}
}
},
"x-permdock-catalog": {
"v": 1,
"generator": "permdock/openapi",
"drafts": {
"oas": "3.3-dev@<commit>",
"securityProfiles": "oai-discussion-5304@2026-09-01"
}
}
}Rules:
- One profile scheme per declared profile.
fapi2becomespermdockFapi2(override with--profile-scheme) withprofileMetadata.name: fapi-20-security-profile. The name map is fixed in the emitter and grows only when the Initiative registers a name; PermDock leaves filing thefapi-20-security-profileregistration to the FAPI Working Group. x-permdock-securityProfileis a single string. PermDock declares one profile per deployment, so the twin stays a string even though the native form allows severaltype: profileschemes.- The target is never inferred.
--targetdefaults to3.2, and a source document that already saysopenapi: 3.3.xstill needs--target 3.3, so a draft shape is only ever emitted on request.--target 3.3writesopenapi: 3.3.0. supportedParametersSchemais PermDock's, one published JSON Schema per profile, at a URL stable across pins.serverscomes from the metadata URL (--metadata-urlorscheme.oauth2MetadataUrl; severalname=urlpairs fill several entries).- One requirement per distinct scope set, listing the authentication methods FAPI 2.0 allows and the grant types the configured flows imply. Operations keep their ordinary
security; the proposal has no operation-level field and PermDock adds none. - The extension twin is always present.
x-permdock-securityProfileis written on theoauth2scheme and every covered operation, as on 3.2 output, so a consumer that strips unknown scheme types still sees the declaration.permdock openapi importreads either form. - Nothing from 3.2 is dropped, and the Overlay form adds
updateactions for the profile scheme and requirements and never removes anything (OpenAPI Overlay). - Validation. No official 3.3 JSON Schema exists, so
--checkvalidates against a PermDock-maintained patch of the 3.2 schema, keyed by the pin and shipped with the CLI, until the official one is published.
The pin
x-permdock-catalog.drafts records every draft revision the output depends on: oas is the v3.3-dev commit, securityProfiles the discussion and date of the implemented design, and overlay joins it with --overlay 1.2. permdock openapi emit --check fails when a committed document's drafts differ from what the installed CLI would write, and permdock doctor warns on the same condition (CLI: doctor). Bumping the pin updates this page, the emitter's name map and patch schema, the permdock/testing fixtures, and a changeset.
When 3.3.0 is released, --target 3.3 emits the released construct in the next minor and drops the draft shape. If field names changed, the importer reads both forms for one minor and --check flags documents still carrying the draft. x-permdock-securityProfile stays on 3.1 and 3.2 output and remains the 3.3 twin for one minor, then becomes opt-in there. The default target moves to 3.3 no earlier than one minor after release. If the Initiative drops Security Profiles, --target 3.3 falls back to the 3.2 shape plus the extension, and documents already emitted keep validating against the pinned patch schema.
GNAP: a reserved name
No Initiative text defines a GNAP security scheme, though funded work to add one is under way. permdock/openapi reserves the scheme kind gnap so a future scheme needs no rename; selecting it is a usage error (exit 2 in the CLI). The community x-gnap extension has no Initiative standing and is never emitted; the importer preserves it untouched. The leaf-to-access-right mapping is on the watch list GNAP section.
The rest of 3.3
Standardized API Features need no action unless one touches security; cookies would matter only to framework session handling, which PermDock does not own. Parameter and form-data changes do not affect authorization output: PermDock emits security, securitySchemes, securityProfileRequirements, responses.403 and extensions only. Moonwalk (4.0) is watched, with no output planned.
Sources
- OpenAPI 3.2.0 release, the
v3.3-devbranch and milestones. - Discussion #5304: Security Profiles, including the expanded design notes.
- OpenAPI Initiative newsletter, June 2026.
- OAI Extension Registry and Namespace Registry.
- FAPI 2.0 Security Profile, Final, section 5.3.4.
Related
- OpenAPI adapter and CLI: openapi: options, flags, recipes.
- OpenAPI registries:
x-permdock-*and the registered names PermDock reuses. - OpenAPI Overlay: delivering the output without editing the source document.
- Arazzo: pre-flighting a workflow against
x-permdock-permissions. - FAPI 2.0 and the watch list.
Last updated on
Standard Schema
How PermDock consumes Standard Schema v1 and Standard JSON Schema so resources can be defined with Zod, Valibot, ArkType or Effect Schema without adapters.
OpenAPI Overlay
How permdock openapi --format overlay emits an Overlay 1.1.0 document (or, behind --overlay 1.2, the pinned Overlay 1.2 draft with reusable actions) that adds security, securitySchemes and x-permdock-* fields to an OpenAPI description without mutating it, how to apply and check it in CI, and why the Overlay never removes security.