8 min read

OpenAPI agent action design that agents can use safely

Design an OpenAPI agent action with bounded inputs, useful results, approval rules, credential control, and safe handling of ambiguous failures.

OpenAPI agent action design that agents can use safely

An OpenAPI document can tell an agent how to call an endpoint. It cannot, by itself, tell the agent what a call is allowed to mean, when a human must intervene, or how the agent should behave after an ambiguous failure. Treating every documented operation as an agent action produces tools that look complete in a demo and become dangerous in ordinary use.

A useful action is smaller than an endpoint. It has a bounded purpose, inputs the agent can justify, a result the agent can act on, an approval decision tied to consequence, and an explicit plan for failures. Do that design work before you wire an operation into an agent. Retrofitting it after the first duplicate charge, accidental production change, or leaked token is a miserable way to learn the lesson.

An operation is not yet an agent action

An HTTP endpoint, an OpenAPI operation, and an agent action answer different questions. People blur them because an OpenAPI operation gives a convenient starting point, but the distinctions determine whether automation stays understandable.

An endpoint is an address such as /v1/deployments. An operation adds an HTTP method, so POST /v1/deployments differs from GET /v1/deployments. An agent action adds the human and operational contract: what goal it pursues, which arguments it accepts, what effects it can produce, what evidence counts as success, and who must consent.

The OpenAPI Specification defines an Operation Object with fields such as operationId, parameters, requestBody, responses, and security. Use those fields as evidence, not as an automatic publication checklist. An operation with a fully specified schema can still be a terrible agent action if its description hides a production effect behind a harmless name.

Consider these two operations:

GET  /v1/projects/{project_id}/builds/{build_id}
POST /v1/projects/{project_id}/builds/{build_id}/promote

The first one retrieves a record. The second one might change traffic, publish artifacts, or alter a release channel. The route only hints at that difference. The action design must state it plainly.

I have seen teams expose a generic request tool because their API already had a clean OpenAPI file. The agent then had permission to assemble arbitrary paths, query strings, and bodies. That is not an action catalog. It is remote code execution against a business API with better punctuation.

Expose an operation only after you can write one sentence in this form: "This action [does a specific thing] to [a bounded object] and returns [evidence of the resulting state]." If you cannot write that sentence without vague verbs such as "manage," "process," or "handle," the action remains too broad.

Start with the consequence, not the request schema

Approval should follow the consequence of a call, not its HTTP verb or the apparent simplicity of its JSON body. A small POST can create an irreversible obligation. A verbose GET can expose private data. A DELETE might only remove a disposable draft, while a PATCH can revoke everyone else's access.

Before reviewing fields, describe the effect in terms a person responsible for the system would recognize. Ask what changes if the server executes the call twice, executes it for the wrong object, or executes it five minutes later than the agent intended. Those questions separate routine retrieval from an action that needs scrutiny.

I use four consequence classes when reviewing a candidate operation:

  • Observation: fetches bounded information and produces no server-side change.
  • Reversible change: creates, updates, or removes something with a documented and practical undo path.
  • External commitment: sends a message, starts a paid job, publishes material, or changes a customer-facing state.
  • Irreversible or broad change: deletes records permanently, rotates access, changes permissions, or affects many objects.

These classes are not a permission model. They force honest descriptions. A "create invoice" operation belongs in external commitment even if the request has only two fields. A "restart environment" operation can become broad change when one environment contains many services.

Do not infer safety from a method name. HTTP defines GET as safe in the protocol sense, meaning the client should not request state changes through it. That is a convention, not proof that a particular server obeys it. I have encountered diagnostic endpoints that refreshed caches, triggered report generation, and consumed scarce capacity when called repeatedly. Test the behavior you have, not the behavior the verb suggests.

Also separate an action's effect from the sensitivity of its result. Fetching an access token might be read-only, but returning that value to an agent defeats the point of controlling the call. Fetching a private customer record may need approval even if the API never changes a byte.

A good action card records both dimensions in plain language:

Action: promote_preview_build
Effect: Changes one named preview build into the staging release channel.
Scope: One project and one build ID.
Result: Release ID, resulting channel, and server timestamp.
Human consent: Required for every call.
Retry: Never retry automatically unless the server accepts the same idempotency token.

That card often exposes missing API semantics before an agent writes a line of code. If nobody can say whether a retry is safe, the action is not ready.

Inputs need boundaries an agent cannot talk its way around

An agent action needs a smaller input contract than the endpoint often accepts. OpenAPI schemas define types and structure, but an agent also needs constraints that prevent it from widening a task through creative arguments.

Take a deployment creation operation. The raw API may support many options for internal clients: environment, artifact reference, region, replica count, environment variables, feature switches, labels, and a free-form configuration object. Giving every field to an agent turns a simple request into an unreviewed administration surface.

Create an action with inputs that match the task. If the task is "deploy the build that passed tests to a preview environment," the agent might need only project_id, build_id, and a short reason. The executor can select the permitted environment and reject anything outside the action's scope.

This request shape makes the boundary concrete:

{
  "project_id": "proj_4821",
  "build_id": "build_9017",
  "reason": "Preview requested after integration tests passed"
}

Do not add a target_url, arbitrary headers, a raw request body, or a general options object merely because the underlying endpoint supports them. Each escape hatch turns your carefully named action back into a generic client.

Use the OpenAPI fields that already carry useful limits. Set additionalProperties: false when an object should accept only named fields. Use enum for a genuinely small set of allowed values. Set length and pattern restrictions when identifiers have an established format. Mark fields required when the executor cannot safely infer them.

For example, this fragment rejects unreviewed configuration fields and makes the intended scope visible in the schema:

DeployPreviewRequest:
  type: object
  additionalProperties: false
  required:
    - project_id
    - build_id
    - reason
  properties:
    project_id:
      type: string
      pattern: '^proj_[A-Za-z0-9]+$'
    build_id:
      type: string
      pattern: '^build_[A-Za-z0-9]+$'
    reason:
      type: string
      minLength: 8
      maxLength: 240

additionalProperties: false prevents a familiar failure: an agent learns from another API example that it can send environment_variables, places secrets or unsafe overrides inside that field, and the server quietly honors them. Rejecting the field gives the agent a useful error rather than a surprise deployment.

Schemas do not replace object-level authorization. A valid project_id can still refer to a project outside the task. The executor must check that the requested object sits within the allowed account, workspace, repository, or environment. Put that check near the action executor, where it cannot depend on an agent's explanation.

Free text needs special treatment. A reason field can help a reviewer, but it should never become an instruction channel for the executor. Store it as an audit annotation. Do not parse it for commands, resource selectors, or permission exceptions.

Expected results must support the next decision

An agent needs an outcome it can reason about, not a raw HTTP response dumped into context. Returning every header, debug field, and nested object increases confusion and may disclose data the agent did not need to complete the task.

Define success in business terms before choosing response codes. For a deployment action, a useful result identifies the deployment, its state, and the location where the server will report later progress. For a record update, it identifies the record and confirms the fields that changed. For a deletion, it confirms the target and whether recovery remains possible.

A compact result for an asynchronous operation might look like this:

{
  "status": "accepted",
  "deployment_id": "dep_2388",
  "project_id": "proj_4821",
  "build_id": "build_9017",
  "target": "preview",
  "operation_status": "queued"
}

That response says something precise: the server accepted work, but deployment has not completed. An agent should not report "deployed" after receiving it. It should either use a separate, read-only status action or tell the user that the operation is queued.

This is where many OpenAPI documents mislead agents. A 202 Accepted response has a specific meaning: the server accepted the request for processing, and processing may not have started or completed. Treating 202 as success in the same sense as a completed 200 creates false claims in logs and user messages.

Separate transport outcome from action outcome. An HTTP 200 might wrap a domain failure such as {"state":"rejected","reason":"build is not eligible"}. Conversely, 409 Conflict may tell the agent that the desired state already exists. The action wrapper should turn these cases into a small set of explicit states such as completed, pending, already_in_desired_state, rejected, and unknown.

Avoid a false promise of uniformity. Some APIs only return an opaque job ID, and that is fine if you expose a status action that can resolve it. The mistake is hiding the gap. State exactly what the first call establishes and what it does not establish.

Filter error detail before it returns to the agent. A server error may include internal URLs, authorization headers, stack traces, or another user's data. The agent needs a reason it can act on, such as "build ID does not belong to project ID," plus a safe correlation ID for a human to investigate. It does not need the upstream exception page.

Approval belongs at the point of commitment

Approve consequential calls
Require one-click or Touch ID approval every time a selected API key is used.

Ask for approval when the call can create a meaningful commitment, and make the approval screen describe the object and effect. Asking once for a vague bundle of future powers trains people to click through a warning they cannot evaluate.

Session approval and call approval solve different problems. Session approval says, "I recognize this agent process and allow it to use this action set while it runs." Call approval says, "I approve this specific consequential request now." Do not substitute one for the other.

An agent that can inspect build status may run for an hour without bothering anyone. An agent that promotes a build should present the project, build ID, release channel, and reason at the moment it asks for consent. A reviewer can judge that request. "Allow deployment tool" gives them almost nothing to judge.

Do not use an approval prompt as a replacement for input validation. If an action lets the agent specify an arbitrary destination or arbitrary permission scope, a reviewer must decipher a large, unstable payload under time pressure. Limit the inputs first. Then approval confirms a bounded action.

The right frequency depends on effect. Require each call for actions that publish, alter access, initiate an external payment, or touch broad production scope. Session consent can fit a group of read-only calls or narrow reversible changes, but only after the process identity and action catalog are visible to the reviewer.

Sallyport applies this distinction with session authorization for a newly seen agent process and optional approval on every use of a particular credential. Its vault gate also denies all actions while locked, so approval cannot turn a locked secret store into an accidental exception.

Do not make a person approve failures that software can prevent. If a build is not eligible for promotion, the executor should reject it before any approval request. Prompts are for legitimate choices, not for asking a tired reviewer to catch malformed state.

A timeout creates an unknown state, not a retry instruction

A network timeout after a mutating request is the failure path that exposes sloppy agent action design. The agent sent the request, then lost the response. The server might have done nothing, completed the change, or still be processing it. The agent cannot discover the truth by assuming its preferred answer.

Walk through a familiar failure. An agent calls POST /v1/invoices with customer, amount, and a request timeout. The connection drops after the server commits the invoice but before the response arrives. The agent sees a timeout, retries with the same data, and the server creates a second invoice. The audit log now says the agent followed its retry policy, which is technically true and operationally useless.

An idempotency token fixes this only when the server actually implements it. The client generates a token once per intended action, sends it with the initial request, and sends the exact same token on a retry. The server must bind that token to the original request and return the original result, or a compatible conflict result, instead of repeating the effect.

Idempotency-Key: act_01HZX7FQ2Z9K8M6R4T3V1W0Y

The action wrapper must keep this token out of the agent's improvisation. Generate it at execution time, persist it with the action attempt, and reuse it only for that attempt. An agent-provided token can collide, be reused across unrelated requests, or become another prompt injection surface.

If the API lacks documented idempotency semantics, do not retry a mutating operation automatically after a timeout. Return unknown with the action identifier and offer a read-only lookup action that can inspect the server's state. If no lookup exists, a human must investigate before anyone repeats the request. That answer feels inconvenient because it is inconvenient. Pretending certainty does not improve it.

OpenAPI can document a header parameter named Idempotency-Key, but documentation alone does not guarantee server behavior. Test it deliberately: send the same token and payload twice, then send the same token with a changed payload. The server should make the first pair converge and reject or clearly handle the changed request. If it silently performs both changes, the header is decoration.

Other failures need their own rules. Treat 401 and 403 as a stop condition, not a cue to hunt for another credential. Treat 429 as a wait condition only when the API communicates a retry delay or your action has a bounded backoff policy. Treat validation errors as feedback the agent can use only when the error identifies a permitted correction.

Authentication does not grant an agent judgment

Keep keys out of actions
Keep API credentials in Sallyport’s encrypted vault while the agent receives only the HTTP result.

An OpenAPI security declaration describes how a client proves its identity to an API. It does not express whether an agent should invoke the operation, whether it may use a particular credential for a particular object, or whether a human needs to review the effect.

The Specification's Security Requirement Object associates an operation with named security schemes. A bearer scheme may tell a client to send an authorization header. Basic authentication may tell it to construct a credential header. That is transport authentication. Do not read more into it than it says.

Keep four questions separate:

  • Who or what calls this action?
  • Which credential does the executor use with the upstream API?
  • Which objects and effects does that credential permit?
  • Which action attempts does a person approve?

When teams merge these questions, they usually hand a token to the agent and call that authorization. The token then appears in tool output, shell history, debug logs, prompts, or a configuration file. Revoking it becomes a cleanup project instead of a single action.

The safer shape keeps the credential in the executor. The agent supplies bounded action inputs. The executor selects an eligible credential, injects it into the HTTP request, evaluates the response, and returns the filtered result. The agent never needs plaintext access to an API key to request an action.

For SSH, apply the same rule. An agent may need to request a command against a named host, but a generic command plus a broadly trusted private key is far wider authority than most tasks require. Restrict host identity, account, command form, and output handling according to the action's purpose.

Sallyport uses this execution model for HTTP calls and SSH commands: credentials remain in its encrypted vault and the agent receives the action result rather than the secret. That design helps only if you still expose narrow actions and choose approvals that fit their effects.

The action description must state what the schema cannot

OpenAPI descriptions matter because agents read them as instructions, but prose should clarify boundaries rather than smuggle in a second, contradictory API contract. Put enforceable limits in schemas and executors. Use descriptions to explain intent, consequence, and conditions that a type system cannot express.

Name operations after the user's intended outcome. getBuildStatus says more than getBuildById; createPreviewDeployment says more than postDeployment. The name should not overpromise. If the server queues work, do not call the operation deployBuild unless its result distinguishes acceptance from completion.

Write descriptions with the details an agent would otherwise guess:

operationId: createPreviewDeployment
summary: Queue one tested build for the preview environment
requestBody:
  required: true
  content:
    application/json:
      schema:
        $ref: '#/components/schemas/DeployPreviewRequest'
responses:
  '202':
    description: Request accepted. Deployment work may still be pending.
  '409':
    description: The build already has a preview deployment or cannot enter preview.

A summary is not enough for higher-consequence operations. Record the intended target, effect class, approval requirement, retry rule, and result states in action metadata that sits beside the OpenAPI document. You can use x- extensions if your tooling owns them, but label them clearly as private conventions. Standard OpenAPI parsers will ignore unknown extensions, which means the executor must enforce them rather than merely display them.

Do not rely on a description that says "use with caution." Caution is a human feeling, not an executable rule. Replace it with a limit: one project, preview only, no arbitrary environment variables, approval for each invocation, and no automatic retry after an unknown result.

Descriptions should also tell the agent when to refuse the action. A promotion operation can require a completed test run. A data export can require a customer-provided case reference. A deletion operation can require a prior lookup that confirms the object is a draft. Those preconditions reduce pointless prompts and make audit records easier to interpret.

Test the action against a careless but capable operator

Verify the action history
Verify Sallyport’s hash-chained audit trail offline over ciphertext, without needing a vault key.

A happy-path test only proves that the API works when every assumption holds. Test an action as though a fast, capable operator has incomplete context, stale identifiers, and a tendency to repeat itself after an error. That is close enough to how autonomous agents fail to be useful.

Build a small test fixture with disposable objects and an account whose permissions match the intended executor. Then run the action through cases that challenge its boundaries:

  • Send an unknown input field and confirm the executor rejects it.
  • Request an object outside the allowed project or workspace.
  • Deny approval and confirm that no upstream request occurs.
  • Force a timeout after the server receives a mutating request.
  • Return a response containing sensitive debug material and confirm filtering removes it.

Inspect more than the final API state. Review the prompt a human sees, the exact request the executor made, the result the agent received, and the audit record. A successful request can still fail the action contract if the prompt hid the target, the result claimed completion too early, or the log cannot distinguish a denied request from an upstream rejection.

For an action with every-call approval, test the ordering. The executor should validate static constraints and resolve enough safe context to show a meaningful request before it asks for consent. It should not send the request first and then seek approval. It should also avoid making a long chain of hidden read calls that expose more data than the final action needs.

Test revoked and expired credentials deliberately. The executor should fail closed, return a safe explanation, and avoid repeated calls with the same unusable credential. A retry loop against a rejected credential can fill logs, trigger rate limits, and make a simple access problem harder to diagnose.

Finally, test cancellation. If a user stops the agent while an upstream job runs, the record should say whether the request never left, reached the server, or entered an unknown state. Cancellation of the local agent process does not necessarily cancel a remote side effect.

Publish fewer actions and make each one defensible

A small action catalog beats a generic API client because each action can carry a reasoned contract. Adding operations is easy. Maintaining truthful result semantics, object boundaries, approval prompts, and failure behavior is where the work lives.

Start with an operation that retrieves a bounded status record. Give it a name that states the object, restrict identifiers to the intended scope, and return only the fields the agent needs. Then add one reversible action and force yourself to write its retry and approval rules before you implement it.

Do not promote an operation into an agent action because an OpenAPI generator can expose it in an afternoon. Promote it when you can explain what happens after a timeout, what the human approves, what the agent sees, and how you will prove later which request occurred. If any of those answers depends on "the agent will probably do the sensible thing," keep the endpoint out of the catalog.

FAQ

What is the difference between an OpenAPI operation and an agent action?

No. An endpoint is an HTTP address, while an OpenAPI operation is one method at that address, such as POST /deployments. An agent action is a narrower contract that adds input limits, result meaning, approval behavior, and recovery rules.

Which API operations should I expose to an AI agent first?

Start with read-only, bounded queries that return records the agent already needs. Do not expose operations that send money, delete data, publish content, or change access until you can define confirmation and recovery behavior precisely.

Is a 200 response enough for an agent to know an action succeeded?

Usually no. A 200 only says the server accepted or completed an HTTP request; it does not tell an agent whether the intended business change occurred. Return a compact result that names the resulting resource, state, and any follow-up work.

Can an agent retry a POST request after a timeout?

Only if the server gives the caller a documented idempotency mechanism and the action wrapper preserves the same idempotency token on retry. A timeout after POST leaves the agent unable to know whether the server acted, so blind retries can duplicate the change.

Does an OpenAPI security scheme provide agent authorization?

OpenAPI can describe a security requirement, such as a bearer token or basic authentication, but that only tells a client how to authenticate. It does not decide whether a particular agent run should make a consequential call at this moment.

Should GET requests always run without approval?

A destructive GET is still destructive, even though it violates HTTP expectations. Set approval based on the effect of the operation and verify the server behavior with a test account before you expose it.

How should I name OpenAPI operationId values for agents?

Operation IDs should name the business intent and object, such as createPreviewDeployment or getInvoiceStatus. Avoid transport names like postV1Deployments, because the agent needs a clue about the consequence, not the route layout.

Should an AI agent receive API keys from an OpenAPI tool?

Do not give the agent the secret. Put credentials in the component that executes the request, inject them at execution time, and return the result or a deliberately filtered error. The agent needs authority to request an action, not a copy of the credential.

Should agent approval settings live in an OpenAPI extension?

Use standard OpenAPI fields for parameters, request bodies, response codes, and security declarations. Keep agent-specific constraints in external action metadata or clearly documented x- extensions, because ordinary OpenAPI clients will ignore extensions they do not understand.

What should I test before giving an API action to an autonomous agent?

Test the action against malformed arguments, excessive scopes, partial success, timeouts, duplicate requests, revoked credentials, and denied approvals. A pleasant demo proves almost nothing; the failure cases tell you whether an agent can operate without making a mess.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov