OpenAPI Design for AI Agents: Stable Primitives, Credential Governance, and Integration Patterns

This article presents a three-layer OpenAPI architecture for AI agent platforms—credential governance, stable primitives, and evolvable management interfaces—emphasizing semantic freezing, platform-issued credentials with request signing, application tiering, and explicit integration authorization to enable third-party systems to integrate reliably without coupling to internal product changes.

James' Growth Diary
James' Growth Diary
James' Growth Diary
OpenAPI Design for AI Agents: Stable Primitives, Credential Governance, and Integration Patterns

MCP optimizes how well the model chooses tools; OpenAPI optimizes whether a third party can survive three rounds of your business rewrites. A portal BFF already exists; adding an auth layer on the outside looks like an OpenAPI. That path is dangerous: the portal can be business-oriented and change frequently, but OpenAPI is the stable face—copying a large, all-in-one aggregation interface forces partners to refactor alongside you. Even more dangerous: the interface may be split correctly, but the credential (ticket) isn't designed—integrators use employee login sessions or invent their own passwords, so the platform cannot issue, revoke, rate-limit, or audit per application; no matter how stable the contract, the integration isn't sound.

Aligning Terminology

Stable Face / Management Face : Stable face = primitives third parties can depend on long-term, semantics change slowly; Management face = configuration interfaces that follow the product, can be larger and change often.

Primitive : Smallest composable capability unit: start chat, stop, continue, resolve identity—not a single monolithic do-everything endpoint.

Application Credential : App ID + Secret issued by the platform to the integrator, representing "this system"; revocable, rotatable.

Ticket Signing : Each request signs timestamp + nonce with the secret, proving "I still hold the key right now."

Replay Protection : A captured request packet cannot be replayed indefinitely; implemented via timestamp window + nonce.

Personal Token : Credential representing a real person, separate from application credential; used for session attribution, permission scoping, billing attribution.

Application Tiering : Three tiers: team / platform-internal / platform-external; external must have explicit integration grant.

Integration Grant : External app must be explicitly authorized to call a specific agent—not "see it, call it."

ACL : Access Control List: holding a ticket doesn't mean you can call every interface.

(Common terms like BFF, SSE, HTTP, UUID, Bearer, HMAC are used directly.)

01 | Third Parties Live on Contracts, Not Pages

Using LEGO bricks vs. finished models to distinguish two interface styles:

What you get : Standard blocks, public specs (LEGO) vs. a pre-assembled whole (finished model)

How to change : Disassemble and recombine freely vs. want to swap a part? Basically buy a new set

Lifecycle : Bricks from 1980 still snap onto today's vs. generation change = obsolete

Suited for : People who want to build their own things vs. people who just want to display

Third-party integrators are people who want to build their own things . They need standard blocks, not a finished product. LEGO's decades-long success rests on one promise: 1980 bricks still fit perfectly with today's —that's exactly what a stable face should be.

This analogy explains three core judgments:

Give bricks, not finished models. Composition rights stay with the integrator, so cut primitives by "one capability one verb" rather than exposing a screen-full aggregation endpoint.

Brick specs must be frozen. If you change the stud dimensions today, every brick the customer bought ten years ago becomes useless. Corresponds to "semantic freeze, prefer addition over mutation."

Entering the LEGO store requires a membership card issued by the store. Maps to credential closed-loop—platform issues, can revoke, can rotate. Using someone else's membership card (employee session impersonation) means the store doesn't know who's browsing and can't ask that individual to leave.

Internal agent platforms have configuration, publishing, permissions, assets, scoring. The portal aggregates for "one-screen completion"—normal. But once exposed verbatim externally, partner code binds to your product cadence: you change one config field, their pipeline breaks.

What Gates Does a Single Integration Call Pass Through?

Ticket flow: from issuance to backbone
Ticket flow: from issuance to backbone

Four Field Scenarios

Scenario A: Full field rewrite after 3 months — Surface symptom: "Partner won't upgrade"; Common misdiagnosis: Push partner to change; Mechanism reality: Stable face and business aggregation face not layered —changing internal config equals changing contract

Scenario B: External app over-privileged into internal capabilities — Surface symptom: "Signature verification missed"; Common misdiagnosis: Only harden signature; Mechanism reality: Missing application tiering : trust domain and must-authorize domain mixed

Scenario C: "Chat works, can't stop" — Surface symptom: "Partner didn't implement stop"; Common misdiagnosis: Add documentation; Mechanism reality: Only chat entry exposed , control-plane primitives incomplete

Scenario D: Partner uses employee account to call — Surface symptom: "Get it working first"; Common misdiagnosis: Add IP allowlist; Mechanism reality: No platform-issued application credential closed-loop —cannot revoke, rotate, rate-limit or audit per app

OpenAPI's first principle: the layer third parties depend on must have predictable change cost; and every call must answer "which platform-issued ticket, representing whom, allowed to do what."

Portal can chase product; OpenAPI must chase contract—stable interface but broken credential governance means integration remains a wild path.

02 | Exposing BFF Directly Breaks in Three Places

Three fractures
Three fractures

"Authenticated HTTP" ≠ "Integrable Stable Face". At least three fractures:

2.1 Audience Mismatch

BFF's reader is first-party frontend, can return "everything this screen needs" in one go. Third-party's reader is another system; it wants composable primitives—start chat, stop, continue, auth, resolve identity. Back to LEGO: give the customer a box of standard bricks, not a glued-together finished model that can't be taken apart. Composition rights must stay with the integrator, so cut by one capability one resource / one verb; avoid screen-level aggregation.

2.2 Change Cadence Mismatch

Product rewrites interaction every two weeks; aggregated JSON shape shakes along. Integrator's release cycle may be quarterly. The two clocks are fundamentally out of sync. Back to LEGO: change the stud dimensions once, and every old brick in the world stops fitting , so stable face must exercise field restraint, semantic freeze; management face uses explicit versioning or separate prefix.

2.3 Trust Boundary Mismatch

Team-internal app, platform-internal service, genuine external partner—if they share the same "verified signature = can call" assumption, the external face quietly reaches internal-only management capabilities. Trust is not transitive, so isolate via application tiering + external integration grant verification .

"Has HTTP auth" also ≠ "credentials are sound". If integrator reuses portal cookie or shares an ops account to call production, platform can't issue tickets, collect tickets, rate-limit per app—primitives however pretty are just naked interfaces. Integration identity must be governable, so the complete chain is platform issues App ID + Secret → request side signs ticket → gateway verifies ticket , optionally layered with personal token.

In evaluations we steal "complete dialogue control-plane primitives + platform-issued credential closed-loop + unified ticket verification / error codes / rate-limiting + application tiering"; reject "BFF wholesale exposure"; reject "employee session impersonation"; reject "all apps same trust level".

Expose BFF verbatim : Gain zero extra design; Reject third party locked to product rewrites

Only expose one monolithic chat endpoint : Gain fast demo; Reject stop / continue / identity resolution missing

Reuse portal login session for third party : Gain connects quickly; Reject cannot revoke / rate-limit / audit per app

Platform-issued credentials + stable primitives + layered management face : Gain integrable, governable, evolvable; Reject requires credential issuance and verification engineering

Per-customer private APIs : Gain short-term deal closure; Reject permanent platform fork

03 | Three Faces: Stable, Credential, Management

Stable face like primitives, credential face must be revocable, management face can chase product—primitives slow-changing, credentials collectable, product runs fast.

Three-Layer Abstraction

Credential Face : Platform issues App ID / Secret; request signature verification; rate-limiting; revocation / rotation; personal tokens. Change attitude: Issuance and revocation are first-class capabilities, not documentation appendix.

Stable Face (Primitives) : Chat SSE, stop, continue, external UUID resolution, integration grant, unified error codes. Change attitude: Semantic freeze; prefer addition over mutation.

Management Face (Evolvable) : Create / configure / publish / assets / tasks / scoring. Change attitude: Can be versioned; allowed to be larger, but don't pretend it shares SLA with stable face .

Reality check (honest internally, desensitized externally): platforms often already expose many management REST endpoints. The point isn't "pretend minimalism" but mental layering : integrators default to depending only on stable face; stepping onto management face means knowingly "riding the product train." Whichever face, must first hold a platform-issued ticket .

OpenAPI three faces
OpenAPI three faces

04 | One Capability One Verb, Control Plane Must Be Complete

🎯 Challenge : Integrators fear "can start chat but can't finish"—disconnects, retries, second round, can't stop. 💡 Solution : Dialogue-related stable face must have at least three pieces: start chat (streaming) / stop / continue ; identity via external UUID resolved server-side; error codes and rate-limiting unified across entry points.

Implementation (illustrative) :

POST /v1/agents/{agent_uuid}/chat          # SSE streaming chat
POST /v1/agents/{agent_uuid}/chat/{sid}/stop
POST /v1/agents/{agent_uuid}/chat/{sid}/continue
GET  /v1/agents/resolve?external_id=...
// Illustrative · TypeScript
// Rule to keep: disconnect is normal, so continue must be first-class, not a doc appendix
async function robustChat(agentUuid: string, message: string) {
  const session = await openChat(agentUuid, message); // SSE
  try {
    await consume(session);
  } catch (e) {
    if (isTransient(e)) await continueChat(agentUuid, session.id);
    else throw e;
  }
}
# Illustrative · Python
# Rule to keep: disconnect is normal, so continue must be first-class, not a doc appendix
def robust_chat(agent_uuid: str, message: str) -> None:
    session = open_chat(agent_uuid, message)  # SSE
    try:
        consume(session)
    except TransientError:
        continue_chat(agent_uuid, session.id)
# Non-transient exceptions bubble up for upper layer to decide retry

Network and host both disconnect, so stop / continue must be designed at same level as chat, not "add later when convenient."

Beyond Verbs: Write Ownership at Creation Moment

A recent real case pushed this principle earlier. A third-party channel (e.g., Zhizhi) batch-created agents via OpenAPI—dozens of clones entered the platform. Problem: platform's clone list was for "users who created clones on the web", so channel's batch clones crowded the list, breaking users' "my clones" mental model.

Our fix wasn't guessing "whose is this" at query time, but write ownership at creation moment : Agent table adds create_source field—platform-internal creates record platform, OpenAPI creates record openapi_${app} (app uses third-party app alias, fallback to app id if alias missing). One record's "who brought it" never needs reverse inference again.

Three decisions at field landing, each with explicit "don't do":

Alias preferred, id fallback. Store openapi_中智渠道 or openapi_app_38291? Humans read alias, so prefer alias; if alias unavailable (e.g., app just created, not configured) fall back to id—ugly is okay, loss is not.

Don't reuse existing field. Table already has a "material source type" field, looks similar but semantics completely different. Stuffing new meaning into old field saves one ALTER TABLE but costs every future query "guessing what this value meant at that time."

Separate write from filter. OpenAPI's /agents/list does not hide anything—integrator lists their own agents, should list all; "channel batch agents don't appear in platform user list" is platform's own clone list rule, implemented by platform side. Who owns the list decides the filter; the writer only ensures "whose" is written correctly.

// Illustrative · Go
// Rule to keep: source normalized at creation write, list semantics don't reverse-invade writer
func ResolveCreateSource(appAliasOrID string) string {
    app := strings.TrimSpace(appAliasOrID)
    if app == "" {
        return "platform" // no app identity = platform self-created
    }
    return "openapi_" + app
}
// Illustrative · TypeScript
function resolveCreateSource(appAliasOrID: string): string {
  const app = appAliasOrID.trim();
  return app === "" ? "platform" : `openapi_${app}`;
}
# Illustrative · Python
def resolve_create_source(app_alias_or_id: str) -> str:
    app = app_alias_or_id.strip()
    return "platform" if not app else f"openapi_{app}"

🛡 Boundary : Source marker written once at creation, never changes with subsequent calls; new channel onboarding doesn't require changing this field's design— openapi_ prefix naturally accommodates the next app name.

05 | Platform-Issued Credentials: No Ticket, No OpenAPI

🎯 Challenge : Primitives complete, but integrator still uses employee login, shared ops account, or "verbal agreement on a fixed header" to call—platform cannot govern per app: leak doesn't know which ticket to revoke, rate-limiting doesn't know who to limit, audit doesn't match contract party. 💡 Solution : Make issue → ticketed call → verify pass → tiered authorization → revoke/rotate a first-class OpenAPI design. Secret generated only by platform, integrator keeps; each request uses secret to prove "I am this app"; when representing a real person, layer personal token or call-user identifier— application identity separate from user identity .

5.1 Onboarding Closed-Loop (Minimum Sound Steps)

1. Apply for access: describe app name, purpose
2. Platform issues: App ID + App Secret (secret visible in full only at generation; integrator must store securely)
3. Configure scope: which paths / capabilities this app may access (ACL); external tier must also bind integration-granted agent
4. Integrator implements signing: sign "timestamp + nonce" with Secret, place in request header
5. Gateway verifies ticket: validate signature, clock window, replay protection → only then enter stable face / management face
6. Incident response: revoke or rotate that app's credentials; rate-limit and audit per app

Integrator is not "one more browser user", so must have independent application identity , cannot reuse portal session. Back to LEGO: equivalent to letting a customer enter the store with your employee badge —what they browsed, how much they took, you can't attribute to them; badge lost means replacing every employee's badge.

5.2 How to Call with Ticket (Illustrative)

// Illustrative · TypeScript
// Platform side: issue full Secret only once; store hash or vault reference internally
async function issueAppCredential(appMeta: AppMeta): Promise<{ appId: string; secret: string }> {
  const appId = newAppId();
  const secret = generateSecretOnce(); // plaintext returned to integrator only at this moment
  await storeApp(appId, hashOrVault(secret), appMeta.level, appMeta.acl);
  return { appId, secret };
}

// Integrator side: every request presents ticket—sign with Secret, not pass Secret as raw Bearer
function signRequest(appId: string, secret: string, user?: string) {
  const ts = unixSeconds();
  const nonce = randomNonce();
  const signature = hmacOrDigest(secret, `${appId}.${ts}.${nonce}`); // algorithm per platform spec
  return {
    "X-App-Id": appId,
    "X-Timestamp": String(ts),
    "X-Nonce": nonce,
    "X-Signature": signature,
    ...(user ? { "X-Call-User": user } : {}),
  };
}

// Gateway: no ticket / bad ticket / expired → reject immediately; only after pass discuss "can touch this agent"
function gate(req: Request) {
  const app = lookupApp(req.headers["X-App-Id"]);
  assertFreshTimestamp(req);         // e.g. ±minutes, replay protection
  assertSignature(app, req);         // proves holds platform-issued Secret
  rateLimit(app);
  // …then tiering and integration grant
}
# Illustrative · Python
# Platform side: issue full Secret only once; store hash or vault reference internally
def issue_app_credential(app_meta: AppMeta) -> tuple[str, str]:
    app_id = new_app_id()
    secret = generate_secret_once()  # plaintext returned to integrator only at this moment
    store_app(app_id, hash_or_vault(secret), app_meta.level, app_meta.acl)
    return app_id, secret

# Integrator side: every request presents ticket—sign with Secret, not pass Secret as raw Bearer
def sign_request(app_id: str, secret: str, user: str | None = None) -> dict:
    ts = unix_seconds()
    nonce = random_nonce()
    signature = hmac_or_digest(secret, f"{app_id}.{ts}.{nonce}")  # algorithm per platform spec
    headers = {
        "X-App-Id": app_id, "X-Timestamp": str(ts), "X-Nonce": nonce, "X-Signature": signature
    }
    if user:
        headers["X-Call-User"] = user
    return headers

# Gateway: no ticket / bad ticket / expired → reject immediately; only after pass discuss "can touch this agent"
def gate(req: Request) -> None:
    app = lookup_app(req.headers["X-App-Id"])
    assert_fresh_timestamp(req)   # e.g. ±minutes, replay protection
    assert_signature(app, req)    # proves holds platform-issued Secret
    rate_limit(app)
    # …then tiering and integration grant

App ID + Secret issued by platform : Integration identity can be opened, closed, not dependent on some employee's tenure

Sign every request : Proves "holds key right now"; key leak → rotate, all old signatures invalid

Timestamp + nonce : Replay protection: captured packet cannot be replayed indefinitely

Call-user identifier separate from App : App = system identity; User = "on behalf of whom"—session and audit both rely on this separation

Path / capability ACL : Ticket ≠ can call all interfaces; chat permission and management permission can be granted separately

5.3 Application Tiering + Personal Tokens

Signature verification only proves "you are this app". Next question: what capabilities does this tier of app have, can it touch this agent?

Team : Serves only own team resources; Allowlist / scope convergence

Platform Internal : High-trust service-to-service; Still audited, never ticketless

Platform External : Genuine third party; Must pass integration grant, default deny "see it, call it"

When system needs to act as a real person (session attribution, permission scoping, billing attribution), use platform-issued personal token : creatable, queryable, revocable, stored hashed or encrypted, displayed desensitized. Personal token is not "more convenient permanent password" but a second class of revocable credential.

// Illustrative · TypeScript
type AppLevel = "team" | "platform_internal" | "platform_external";

function authorize(app: App, agentId: string, user?: User) {
  verifySignature(app);             // first prove holds platform-issued app credential
  rateLimit(app);
  if (app.level === "platform_external") {
    assertIntegrationGrant(app, agentId); // external must have explicit integration grant
  }
  if (needsUserContext(app)) {
    assertPersonalTokenOrCallUser(user);  // system acts on behalf of user
  }
}
# Illustrative · Python
from typing import Literal

AppLevel = Literal["team", "platform_internal", "platform_external"]

def authorize(app: App, agent_id: str, user: User | None = None) -> None:
    verify_signature(app)             # first prove holds platform-issued app credential
    rate_limit(app)
    if app.level == "platform_external":
        assert_integration_grant(app, agent_id)  # external must have explicit integration grant
    if needs_user_context(app):
        assert_personal_token_or_call_user(user)  # system acts on behalf of user

🛡 Boundaries :

Full Secret plaintext only at issuance moment given to integrator; console only shows desensitized view.

Leak response is revoke / rotate that app , not change site-wide password.

Even if management large interfaces are exposed, they must run same ticket verification and live in "management face" narrative.

Forbidden to hand out "container direct address + long-lived static token" to third party for speed—that's a bypass, not OpenAPI.

Signature pass only proves "you are this app", not "you should touch this agent"; while without platform-issued app, you can't even prove "who you are" .

06 | Three Scenario Acceptance Tests

Acceptance paths
Acceptance paths

Scenario A: External System Embeds Chat

Apply to platform → receive App ID / Secret (secret shown once).

Configure ACL + complete integration grant binding agent.

Every request signs ticket; only chat / stop / continue + UUID resolution to complete.

Decision point: if employee portal login still works against production → Reject ; must present app credential → Accept .

Decision point: if still must call "full save role" to start chat → Reject ; primitives self-contained → Accept .

Scenario B: Product Rewrite

Portal aggregation interface adds/removes fields.

Stable face contract regression: field semantics unchanged, test suite green.

Decision point: if third-party pipeline forced to change → stable face mistaken for management face.

Scenario C: Credential Leak

Integrator reports Secret possibly leaked.

Platform revokes or rotates that app's credentials; old signatures immediately 401.

Decision point: if only option is "change everyone's password / shut down entire OpenAPI" → no per-app issuance-revocation closed-loop.

Issuance : Platform can issue App ID / Secret; plaintext visible only at generation

Ticket Presentation : Missing or invalid signature → stable face and management face both unreachable

Primitive Completeness : Start / stop / continue + identity resolution independently documentable

Layer Declaration : Docs explicitly distinguish stable face / management face

Tiering : External app without grant fails to call target agent

Revocation : Single app revocation doesn't affect other integrators

Backbone : After ticket verification still enters same agent gateway, no container bypass

07 | Selection: How Four Faces Divide Labor

Selection: how four faces divide labor
Selection: how four faces divide labor

Three-Layer Value

⚡ Quantified Efficiency : Partner onboarding shifts from "shadow internal config + borrow account" to "collect ticket + integrate fixed primitives"; incident response revokes per app, no full-site shutdown

📥 Capability Sink : Teams that can't build portals can still embed agents in own systems with auditable identity

📚 Pattern Upgrade : From "expose BFF" to "governable credentials + stable primitives + evolvable management face"

Five-Dimension Selection

Primary Reader : Stable Face OpenAPI = Third-party systems; Management Face OpenAPI = Automation / ops systems; Portal BFF = Frontend; Inbound MCP = Host model

Change : Stable Face = Slow; Management Face = Medium; Portal BFF = Fast; Inbound MCP = Tool set restrained

Granularity : Stable Face = Primitives; Management Face = Can aggregate; Portal BFF = Strong aggregation; Inbound MCP = Lifecycle

Trust : Stable Face = Platform-issued ticket + tiering + grant; Management Face = Same as left; Portal BFF = Login session; Inbound MCP = User ticket + config identity

Failure Cost : Stable Face = Contract-level; Management Face = Pipeline-level; Portal BFF = Page-level; Inbound MCP = Single tool-level

08 | Where Not to Use, Five Pitfalls, How to Grade Changes

Applicability boundaries and five pits
Applicability boundaries and five pits

Applicable / Not Applicable

Applicable : Embed published agents into enterprise systems; Need version commitment and per-app audit; Multi-app, multi-trust-domain, need revocability

Not Applicable : Use OpenAPI to "replace" product thinking; Permanently expose internal debug interfaces; Assume "verify signature = universal pass" or "borrow employee number first"

Pit 1: BFF Wholesale as OpenAPI

Temptation : saves effort. Wrong antidote : add gateway auth and ship. Right antidote : extract stable face primitives; explicitly label management face as evolvable.

Pit 2: Only Chat, No Stop / Continue

Demo only does start chat, looks sufficient. When integrator goes live, network jitter needs retry, user clicks cancel needs stop—interface simply doesn't support—forcing them to "retry whole segment", burning tokens for a whole long conversation. Control plane must ship at same level as start chat.

Pit 3: Internal/External Apps Same Privilege

Temptation : simple permission model. Wrong antidote : rely entirely on IP allowlist. Right antidote : application tiering + external integration grant.

Pit 4: Open Bypass for Speed

Third party wants low latency, so hand out container direct address, bypass gateway. Short-term latency drops, cost is this call path completely invisible to ticket verification, rate-limiting, audit—compliance reports later reveal this segment is blank. Speed via warm-up, not bypass.

Pit 5: No Platform Credentials, Use Login Session as Stopgap

Integration rushed, so put someone's portal cookie or personal account into partner's config. Once this hole opens, platform neither knows who's calling nor can stop just that one party—only change that person's password or shut down entire OpenAPI. Correct: formally onboard, issue App ID / Secret, partner signs ticket, leak → revoke only that app.

L1 / L2 / L3

L1 : Doc layering, error codes and rate-limiting aligned; write "must hold platform-issued credential"

L2 : Complete stop / continue; UUID resolution; enforce signature verification in production

L3 : Application tiering, integration grant, personal tokens, per-app revocation / rotation

Summary

OpenAPI's reader is a system; acceptance is contract stability + credential governability, not page feature parity.

One capability one verb: start chat, stop, continue, identity resolution—primitives first.

Portal can be large, can change often; stable face exercises field restraint, semantics slow-changing.

Credentials must be sound: platform issues App ID / Secret → integrator signs ticket → gateway verifies; no ticket, no call.

Application tiering + integration grant: signature verification ≠ allowed to touch this agent; personal token solves "on behalf of whom".

Management face may exist, but must be explicitly layered, and also run ticket verification—don't pretend it shares SLA with stable face.

After ticket verification still goes through same agent backbone—bypass is not an integration accelerator.

OpenAPI stabilizes for integrating systems: primitives composable, business rewrites not kidnapping; platform issues ticket, present ticket to call—copying portal or borrowing accounts are both anti-AI-native wild paths.

Next article will cover CLI Usage: Scenario + Skill, Not OpenAPI Shell —the face for AI operators, can't be wrapped one-to-one from interface table.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Platform EngineeringauthenticationAPI DesignAI AgentAuthorizationSystem IntegrationOpenAPICredential ManagementAPI GovernanceStable Contracts
James' Growth Diary
Written by

James' Growth Diary

I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.