8 min read

API response minimization for safer AI agent context

API response minimization keeps unnecessary personal, financial, and operational data out of AI agent context through narrow response contracts.

API response minimization for safer AI agent context

AI agents do not need a copy of every object they touch. They need enough information to make the next decision, carry out the action, and report what happened. When an API returns a complete customer record, invoice, ticket, repository setting, or incident object to an agent that only needs an ID and a status, the API has already expanded the data exposure problem.

This is easy to miss because the request may be read-only, authenticated, and sent over TLS. None of that changes what happens next. The response can enter an agent transcript, a tool trace, a model request, a local cache, a bug report, or a human review queue. If the agent can read it, you should assume the agent context now contains it.

The practical fix is API response minimization: define the smallest useful response for each agent task, make that shape easy to request, and make broad data an exceptional path with human scrutiny. This is not about making JSON prettier. It is about reducing the number of places where personal, financial, and operational data can turn up after a routine automated call.

An authenticated read can still disclose far too much

Read access limits writes. It does not limit copying, summarizing, quoting, or accidentally sending data to a different tool. Teams often call an agent "read-only" as though that settles the risk. It settles only one class of risk.

Consider an agent assigned to identify overdue invoices and open a follow-up task. The agent needs an invoice ID, account ID, due date, amount, currency, and collection state. A conventional invoice endpoint may also return billing and shipping addresses, tax identifiers, payment processor references, line item descriptions, a finance user's internal memo, and the full payment history. Every extra field creates another fact the agent can repeat when it does not need to.

Operational data has the same problem. A task that checks whether a deployment completed may need a service name, build identifier, state, and failure category. It rarely needs an entire environment export containing hostnames, internal addresses, command output, incident comments, or unrelated configuration.

The distinction people blur is important: authorization answers whether a caller may access a resource; minimization answers how much of that resource the caller receives for this particular job. A token with permission to read invoice:123 may be correctly authorized and still receive an unsafe representation of that invoice.

The IETF's RFC 9110 describes representations as information intended to reflect the current or desired state of a resource. It does not require one maximal representation per resource. That is useful room to design. A resource can have a summary representation, an operational representation, and a finance representation, provided the API makes each contract clear.

Do not rely on a prompt instruction such as "ignore personal data." Prompts influence behavior; response shape controls exposure. If an endpoint ships a home address, the agent has already received it before it decides whether to ignore it.

Start with the work the agent must complete

A safe response contract starts with the decision an agent must make, not with an existing database model. Write the task in a sentence, then list the facts that change the action. Everything else must justify its presence.

For example, an agent that retries failed build jobs may need this response:

{
  "job_id": "job_4821",
  "state": "failed",
  "retryable": true,
  "failure_class": "transient_dependency",
  "attempts_remaining": 1
}

It does not need the full build log to decide that retry is allowed. If a human later needs diagnostics, provide a separate endpoint with a narrower audience and an explicit reason to fetch it. A log endpoint should also support bounded ranges, because full logs contain tokens, customer inputs, paths, and fragments of configuration more often than anyone admits.

Build a small task matrix before changing endpoints. It forces conversations that otherwise stay vague:

Agent taskDecision fieldsAction fieldsFields excluded by default
Create support follow-upticket ID, priority, categoryaccount ID, assignee queuemessage body, attachments, internal notes
Retry a jobjob ID, state, retryableretry token or job IDcomplete log, environment values
Flag overdue invoiceinvoice ID, due date, amount, stateaccount IDaddress, tax data, payment references
Check service healthservice ID, state, error classincident IDhost details, raw diagnostics

A field belongs in the response only if it changes the branch the agent takes, appears in the action request, or must appear in its user-facing report. "It might be useful later" is not a good reason. That phrase is how list endpoints acquire fifty fields and nobody knows who depends on any of them.

The exercise also exposes fields that should be computed instead of disclosed. An agent does not need a payroll record to learn whether expense approval needs a manager. Return approval_required: true. It does not need every entitlement to learn whether a deployment may proceed. Return deployment_permitted: false and a stable reason code.

This is not security through obscurity. It is a deliberate API contract that gives callers the outcome they need without handing them the underlying record.

Default objects should be summaries, not database rows

The most reliable design gives ordinary list and lookup calls a safe summary by default. Make detailed representations explicit, separately authorized, and uncommon. Requiring every caller to remember a restrictive query option will fail eventually, especially when a library adds a convenience method that omits it.

A customer summary might look like this:

{
  "id": "cus_7f31",
  "display_name": "Northwind Parts",
  "account_state": "active",
  "open_invoice_count": 2,
  "support_tier": "standard"
}

Do not return email, phone, street address, tax identifier, payment instrument metadata, or free-form notes because a customer row happens to contain them. Some of those fields may be necessary for a billing application. They do not belong in a summary contract used by an operations agent.

There are two workable patterns. A separate summary endpoint, such as GET /customers/{id}/summary, is blunt and easy to audit. A projection parameter, such as GET /customers/{id}?view=summary, can work when it has a fixed, documented set of views. Both are better than an endpoint that returns everything and asks each client to ignore what it did not need.

Avoid a generic expand=* or include=all switch for agent-facing credentials. It becomes the path of least resistance during debugging, then stays in production because removing it feels risky. If a detail representation is required, name it after the task: view=collections, view=deployment_status, or view=case_triage. Task names force a design review. "All" avoids one.

A common objection is that separate views duplicate code. They do duplicate some mapping code. That cost is small compared with investigating why a tool transcript contains a tax number or an internal incident note. The mapping layer is also where you document ownership and test the promise that an agent view excludes sensitive columns.

Field selection must use an allowlist, not a parser trick

A fields parameter can reduce responses well, but only when the server treats it as a strict allowlist. A loose parser turns a convenience feature into a data extraction interface.

This request is reasonable:

GET /v1/invoices?state=overdue&fields=id,account_id,due_date,amount,currency,collection_state&limit=25

The server should return only fields allowed for this endpoint and credential. If a caller asks for billing_address or payment_reference, reject the request with a clear error. Do not silently add sensitive fields, and do not accept arbitrary nested paths such as customer.*.

A response contract might state the behavior precisely:

{
  "error": {
    "code": "unsupported_field",
    "message": "Field 'payment_reference' is not available in the agent invoice view",
    "allowed_fields": [
      "id",
      "account_id",
      "due_date",
      "amount",
      "currency",
      "collection_state"
    ]
  }
}

The error itself needs discipline. Never include the rejected field's value, nearby record data, a stack trace, raw query text from another service, or a database error. Error bodies often become an accidental second API, particularly when engineers make them verbose to speed up an incident.

GraphQL deserves the same scrutiny. People assume clients can request only what they name, which helps, but a schema can still expose sensitive fields, nested relations can multiply records, and aliases can make a single query hard to reason about. Set depth and complexity limits, disable or restrict introspection where appropriate for the environment, and authorize fields rather than only top-level objects. More importantly, create an agent schema or persisted queries for the handful of approved tasks. A broad schema plus a polite instruction is not a narrow interface.

The OWASP API Security Top 10 calls out broken object property level authorization. Its concern is often framed as a caller retrieving a property it should never access. Agent use adds another failure mode: the caller may technically have access, but the task does not require the property and should not distribute it into model context. Keep both tests. Ask "may this credential read it?" and then ask "why does this task need it now?"

Pagination controls volume, but filters control relevance

Revoke a risky agent run
Its Sessions journal lets you revoke an active agent run immediately when an investigation changes scope.

A response with ten records is not automatically a small response. If each record contains a large nested object or a long text field, pagination merely divides the leak into neat pages.

Use filters that express the agent's work. A collections agent should query overdue invoices in a stated state and date range. It should not list every invoice and decide locally which ones matter. A deployment agent should ask for the one service and current release, not query every environment and then search the result.

Cursor pagination also needs a careful response shape. A cursor should be opaque and should not embed an email address, account name, unencrypted filter values, or an internal database key that reveals ordering. Clients will put cursors in logs and tickets. Treat them as data that travels.

Keep page limits conservative for agent credentials. A small limit does more than reduce token use. It provides a pause point where the agent can inspect a summary, choose a relevant record, and make a targeted follow-up call. That sequence is safer than loading a complete account history because a task began with the words "investigate this customer."

Do not confuse a search endpoint with permission to return every matching detail. Search should usually return a result card: stable ID, label, state, and perhaps a match reason. The caller can fetch a permitted detail view after it selects a record. This two-call pattern feels less convenient than a giant result object, but it makes the transfer of sensitive information visible and reviewable.

Free text and nested records need their own boundary

Structured fields are easier to classify than text written by humans. Free-text fields absorb names, phone numbers, credentials pasted by mistake, allegations, health details, legal advice, and internal opinions. A ticket's description looks harmless in a schema review until someone reads a real week of tickets.

Treat comments, notes, descriptions, attachments, logs, and message bodies as sensitive by default for autonomous workflows. Return a category, a short server-generated classification, or a count instead when that is enough to choose an action. For instance, an agent may need has_customer_reply: true and latest_message_at, not the message itself.

Do not ask the model to redact arbitrary text after retrieval. That approach is popular because it appears to preserve a single broad endpoint. It fails in two ways. First, the raw content has already entered the agent context before redaction. Second, model-generated redaction is probabilistic, so a partial name, account number, or quote can survive.

If a task genuinely requires text, put hard bounds around the request. Fetch one message by ID rather than a full thread. Request a known character limit enforced by the server. Remove attachment content unless a user has approved that specific retrieval. State what the client will receive when truncation occurs, such as content_truncated: true, so the agent does not invent missing details.

Nested data creates a quieter version of the same failure. A response that includes customer, contacts, invoices, payments, and events may look like one object in application code. For exposure purposes, it is a bundle of independent datasets. Require separate endpoints or explicit, allowlisted expansions for each relation. Then test the worst ordinary query, not just the happy path that returns one sparse record.

Error handling and observability can recreate the leak

Lock sensitive calls at the vault
While the vault is locked, Sallyport denies every agent action before an HTTP request can run.

Teams often narrow the successful response and then copy the original payload into debug logs, tracing attributes, retry queues, and exception reports. That work has moved the data; it has not reduced its exposure.

Inspect the whole call path. At minimum, examine the agent tool wrapper, HTTP client debug mode, request recorder, distributed tracing configuration, error reporting service, job queue, local transcript storage, and support workflow. The places that claim they log "only metadata" deserve a direct test rather than trust.

Run a canary record through a nonproduction environment. Give it distinctive fake values in fields that must never reach agent context, such as CANARY_BILLING_ADDRESS_927 and CANARY_INTERNAL_NOTE_927. Execute the actual agent task, then search every permitted log and trace store for those strings. Repeat for a failed request, a timeout, and a malformed response. Success-path tests miss most accidental payload capture.

A useful call journal records the action without duplicating content:

{
  "time": "2025-03-08T14:03:12Z",
  "caller": "release-agent",
  "operation": "GET /v1/jobs/{id}/retry-status",
  "resource_id": "job_4821",
  "response_view": "retry_status",
  "field_set": ["job_id", "state", "retryable", "failure_class"],
  "result_count": 1,
  "outcome": "200"
}

Record identifiers only where your own retention and access rules allow them. In higher-risk systems, store a keyed reference or a short-lived correlation ID instead. A hash can also leak if the original value comes from a small, guessable domain, so do not call hashing redaction without considering what an attacker can enumerate.

Sanitize outbound error messages at the server boundary. A database driver may expose a failed SQL fragment. An upstream service may send a whole record in an error envelope. Your API should map those failures to stable public codes, retain detailed diagnostics in a restricted store, and include no response body in agent-facing errors by default.

Separate capability from disclosure in the agent gateway

An action gateway should hold the credential and execute the request, but it should not treat any response available to that credential as suitable agent context. Secret isolation and response minimization solve different parts of the same call.

Sallyport keeps API and SSH secrets in its encrypted vault and returns action results to the agent rather than exposing the secret itself. That protects the credential, while the API owner still has to decide whether the result contains an unnecessary account record, command output, or operational detail.

Give each agent task a named request template where possible. The template fixes the method, host, path shape, permitted query fields, page limit, and accepted response view. A release-status template can allow a single service ID and return a short status object. It should not accept an arbitrary URL and an arbitrary fields expression just because both are easy to pass through.

This is where broad proxy thinking causes trouble. A generic HTTP forwarder can be useful for development, but it cannot express the difference between "check this deployment" and "download every artifact log." Put the intent in the callable action. When a new task needs more data, require an API change or a new template. That friction is the point: somebody has to say why the extra data must enter context.

Human approval still has a role for exceptions. If an agent needs the content of one support message to resolve a case, a person can approve that particular call after seeing the destination and scope. Approval should not become the normal substitute for narrow responses. People approve familiar cards quickly, especially during an incident, and recurring approvals train them to stop reading.

Test the absence of fields as part of the contract

Route agent HTTP through a gateway
Route HTTP requests through the bundled MCP shim instead of placing bearer, basic, or custom-header secrets in prompts.

Most API tests assert that expected fields exist. Agent-facing APIs also need tests that forbidden fields do not exist, including when code takes a fallback path.

Keep a denylist test next to every response view. Use realistic field names, including nested relations and free text. The test should fail if serialization adds one later through an ORM default, a shared DTO, or an eager-loaded relation.

forbidden = {
    "email",
    "phone",
    "billing_address",
    "tax_id",
    "payment_reference",
    "internal_note",
    "attachments",
}

body = get_invoice_agent_view("inv_1042")
assert forbidden.isdisjoint(body.keys())
assert "customer" not in body
assert "events" not in body

That simple test catches only top-level fields. Add serialization tests that walk the entire JSON tree, and test list, search, error, and export endpoints separately. The worst leak often comes from a collection response that reuses a full detail serializer because it saved a few lines of code.

Contract tests should also assert response size boundaries. A strict byte limit will not fit every object, but a reasonable ceiling alerts you when a developer adds an unbounded text field or a relation. Pair it with a fixture containing long notes and many child records, otherwise the test gives false comfort.

Review changes by asking three direct questions: Which agent task needs this field? Which response view includes it? What test proves it stays absent everywhere else? If the author cannot answer, do not merge the field into a broadly callable endpoint.

Make exceptional detail retrieval visible and temporary

Some work really does require sensitive detail. Fraud review, account recovery, a security investigation, and a difficult support case cannot run entirely on summaries. The answer is not to pretend otherwise. The answer is to make that detail retrieval explicit, short-lived, and bounded to the exact record.

Use a separate endpoint or action that takes a stable record ID and a declared purpose. Return the smallest slice needed, such as one disputed payment field or one selected customer message. Do not grant access to a full account export because the same case contains one disputed charge.

For higher-risk retrieval, require a human to approve the individual call and record the caller, purpose, view, record reference, and outcome. Keep the audit record separate from the sensitive response body. You need to know that a retrieval occurred without creating another casual copy of the information.

A mature API makes the safe path the easy path. Summary views should have clear names, good documentation, and stable fields. Broad detail endpoints should feel deliberate because they carry more responsibility. If your agent repeatedly needs a sensitive field, do not normalize the exception. Revisit the task design and ask whether a server-side decision or a redacted derived value would do the job.

The first useful audit is usually a list endpoint, not the endpoint everyone already fears. Capture one real agent task, mark every field it used, and compare that list with the response it received. The unused portion is your next API change.

FAQ

How do I decide which API fields an AI agent actually needs?

An agent only needs the facts required to choose and execute its next action. Return identifiers, status, and the narrow business fields that support that action; fetch sensitive detail only through a separate, justified call. Treat the context window as a distribution channel, because it is one once the response arrives there.

Is pagination enough to protect sensitive API responses?

Pagination limits volume, but it does not decide whether each returned record contains inappropriate fields. A page of ten records can still expose addresses, payment references, or internal notes. Use both pagination and field selection.

Should I remove sensitive fields from an existing API endpoint?

Usually, no. A general read endpoint has too many callers and too many future uses, so reducing it can break legitimate software. Add a task-specific projection or an opt-in fields parameter, then migrate agent callers deliberately.

Are read-only API credentials safe for autonomous agents?

A read-only token still allows data to leave its original system and enter prompts, logs, transcripts, and model-provider processing paths. Read permission limits mutation, not disclosure. Scope reads to the smallest resource and projection that work.

How should a fields parameter be designed safely?

Use explicit allowlists, reject unknown names, and return a documented default projection when the client omits the parameter. Do not accept arbitrary object paths or generic include expressions without strict validation. The response schema must remain predictable enough to review and test.

Can internal notes be exposed to an AI coding agent?

Internal annotations often contain the most damaging material: incident details, customer complaints, risk flags, escalation comments, and copied messages. Mark them as staff-only fields and keep them out of agent projections unless a tightly defined workflow needs them.

What should I log when an agent calls a sensitive API?

You need evidence of the request path, caller identity, response projection, result size, and any approval for exceptional access. You do not need to copy every sensitive value into the observability system. Metadata proves the control without recreating the leak.

Do model providers retain API data placed in agent context?

Many providers retain prompts or inputs under terms that differ by plan and configuration, and your own agent runner may retain transcripts too. Do not make a privacy promise based on an assumption about retention. Prevent unnecessary data from entering context before those terms matter.

Which endpoints should I audit first for overbroad responses?

Start with endpoints that return lists, search results, account objects, invoices, tickets, exports, and error payloads. Compare the full response to the fields the agent used in its final action. Large objects with copied notes or nested related records usually produce the quickest reductions.

Does credential isolation solve API response data exposure?

Keep credentials outside the agent and keep response content narrow. A credential gateway can stop secret disclosure, but it cannot make an oversized response safe after the agent receives it. You need both controls.

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