Skip to content
← All articles
protocolAdvanced12 min readLast updated: Apr 30, 2026

OpenAPI Catalog + OAuth Discovery: Autonomous API Access

Let AI agents discover your API and complete the OAuth dance without human input. AIDE's proto-openapi-catalog and proto-oauth-discovery checks.

  • #openapi
  • #oauth
  • #discovery
  • #well-known
  • #advanced

For an agent to use your API autonomously it needs two things: (1) what endpoints exist and what they want, (2) how to authenticate. This guide pairs RFC 8414 OAuth Authorization Server Metadata with an OpenAPI 3.1 well-known catalogue so an agent can consume your API without human handholding. AIDE's proto-openapi-catalog and proto-oauth-discovery checks verify the wiring.

What AIDE actually checks

proto-openapi-catalog — three steps:

  1. Does https://<domain>/.well-known/openapi return HTTP 200?
  2. Does the body parse as a valid OpenAPI 3.1 document?
  3. Does at least one operation include x-ai-tags or equivalent AI metadata?

proto-oauth-discovery — two steps:

  1. Does https://<domain>/.well-known/oauth-authorization-server return HTTP 200?
  2. Does the body include the RFC 8414 mandatory fields (issuer, authorization_endpoint, token_endpoint, response_types_supported)?

When both PASS, an agent dispatcher can discover your API and complete OAuth automatically.

Step 1 — OpenAPI 3.1 document

If you use FastAPI or Flask-OpenAPI the spec is already auto-generated. Map it onto the well-known path:

# FastAPI example — apps/api/src/aide_api/main.py
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI(
    title="AIDE Scan API",
    version="1.0.0",
    description="REST API for AI-readiness scans",
)

@app.get("/.well-known/openapi", include_in_schema=False)
async def well_known_openapi():
    """Discovery-friendly OpenAPI document."""
    schema = app.openapi()

    # Enrich for AI agents
    schema["info"]["x-ai-friendly"] = True
    schema["info"]["x-mcp-server"] = "https://mcp.aide.tr/v1/rpc"

    # Tag every operation (example)
    for path, methods in schema["paths"].items():
        for method, operation in methods.items():
            if isinstance(operation, dict):
                operation.setdefault("x-ai-tags", _infer_ai_tags(path, operation))

    return JSONResponse(
        schema,
        headers={
            "Content-Type": "application/json",
            "Cache-Control": "public, max-age=3600",
            "Access-Control-Allow-Origin": "*",
        },
    )


def _infer_ai_tags(path: str, op: dict) -> list[str]:
    """Suggest AI tags from path — dispatchers use this as a filter."""
    tags: list[str] = []
    if "/scans" in path:
        tags.append("audit")
    if "/leaderboard" in path:
        tags.append("benchmark")
    if op.get("method", "").upper() == "GET":
        tags.append("read-only")
    return tags

x-ai-tags is non-standard but increasingly common. AIDE accepts variants such as x-ai-friendly, x-llm-tags, x-agent-skills.

Step 2 — OAuth Authorization Server Metadata

RFC 8414 defines a standard well-known endpoint:

// /.well-known/oauth-authorization-server
{
  "issuer": "https://aide.tr",
  "authorization_endpoint": "https://aide.tr/oauth/authorize",
  "token_endpoint": "https://aide.tr/oauth/token",
  "registration_endpoint": "https://aide.tr/oauth/register",
  "scopes_supported": ["scan:create", "scan:read", "leaderboard:read"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
  "pkce_required": true,
  "device_authorization_endpoint": "https://aide.tr/oauth/device",
  "x-ai-dynamic-registration": true
}

Critical fields:

| Field | Why | |---|---| | registration_endpoint | Dynamic Client Registration (RFC 7591) — agents register themselves without human ops | | code_challenge_methods_supported: ["S256"] | PKCE is mandatory — safe for public clients | | device_authorization_endpoint | Device flow (RFC 8628) — for headless agents | | pkce_required: true | PKCE on every flow — combined with none auth this is ideal for agents | | x-ai-dynamic-registration | AIDE flips PASS — signals automatic client onboarding is supported |

Step 3 — Dynamic Client Registration

Agents can't pre-configure a static client_id (every agent is different). RFC 7591 dynamic registration handles this:

from fastapi import APIRouter
from pydantic import BaseModel
import secrets
from datetime import datetime

router = APIRouter()


class ClientRegRequest(BaseModel):
    client_name: str
    redirect_uris: list[str]
    grant_types: list[str] = ["authorization_code", "refresh_token"]
    token_endpoint_auth_method: str = "none"  # PKCE-friendly public client
    scope: str = "scan:read"


@router.post("/oauth/register")
async def register_client(req: ClientRegRequest):
    client_id = f"agent_{secrets.token_urlsafe(16)}"

    await db.clients.insert({
        "client_id": client_id,
        "client_name": req.client_name,
        "redirect_uris": req.redirect_uris,
        "grant_types": req.grant_types,
        "auth_method": req.token_endpoint_auth_method,
        "scope": req.scope,
        "created_at": datetime.utcnow(),
        "is_agent": True,  # mark for filtering / bulk revocation
    })

    return {
        "client_id": client_id,
        "client_id_issued_at": int(datetime.utcnow().timestamp()),
        "redirect_uris": req.redirect_uris,
        "grant_types": req.grant_types,
        "token_endpoint_auth_method": req.token_endpoint_auth_method,
        "scope": req.scope,
    }

PKCE + public client (token_endpoint_auth_method: "none") is the safest combo for agents — no secret to leak.

Step 4 — PKCE flow

The agent runs the authorization-code flow with PKCE for the requested scope:

import hashlib, base64, secrets

# 1. Code verifier
code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()

# 2. Authorization redirect — user approves in a browser
auth_url = (
    f"https://aide.tr/oauth/authorize?"
    f"response_type=code&client_id={client_id}&"
    f"redirect_uri={redirect_uri}&"
    f"code_challenge={code_challenge}&"
    f"code_challenge_method=S256&"
    f"scope=scan:create"
)

# 3. After approval, exchange code for token
token_resp = await client.post(
    "https://aide.tr/oauth/token",
    data={
        "grant_type": "authorization_code",
        "code": auth_code,
        "redirect_uri": redirect_uri,
        "client_id": client_id,
        "code_verifier": code_verifier,  # PKCE
    },
)
access_token = token_resp.json()["access_token"]

Common mistakes

| Mistake | Symptom | Fix | |---|---|---| | OpenAPI securitySchemes empty | Agent can't discover auth | Define oauth2 flow + scopes under securitySchemes | | .well-known/openapi returns 200 but info.title empty | AIDE WARNING | Validate the document on every build (Spectral linter in CI) | | Dynamic registration returns 401 | Treated as a privileged endpoint | The endpoint must be anonymous; protect with rate limiting | | redirect_uris whitelist excludes http://localhost | Local-dev agents fail | Allow http://localhost:* pattern for dev/test | | scopes_supported empty | Agent doesn't know what to ask for | Publish ≥2–3 meaningful scopes (read, write, admin) |

Testing — what AIDE does

# OpenAPI catalog
curl -s https://your-site.com/.well-known/openapi | jq '.openapi, .info.title, (.paths | keys | length)'
# Expect: "3.1.0", "Site API", >0

# OAuth discovery
curl -s https://your-site.com/.well-known/oauth-authorization-server | jq '.issuer, .token_endpoint, .scopes_supported'
# Expect: all populated

# Dynamic registration probe
curl -X POST https://your-site.com/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{"client_name":"test","redirect_uris":["http://localhost:8080/cb"]}'
# Expect: 200 with a client_id in the body

If all three hold, agent dispatchers can complete register → PKCE → API call without human input.

Production hardening

  • Token TTL: access token 1 h, refresh token 30 d. Shorter = more agent traffic; longer = bigger compromise window.
  • Audit log: Persist every grant + exchange in oauth_grants. Mark agent clients (is_agent: true) — useful for bulk revocation.
  • Rate limit: /oauth/register is an abuse vector — 10 registrations/minute is plenty.
  • Scope inflation: Agents tend to ask for admin. Enforce least-privilege defaults; escalation is a separate flow.

Related resources

1284 subscribers

Weekly AI-Readiness newsletter

New articles, industry trends, check updates — one email a week.

Sitenizde deneyin

Tek bir tıklamayla bu kontrolü çalıştırın.

Which check do you want?
OpenAPI Catalog + OAuth Discovery: Autonomous API Access | AIDE