Skip to content

ECO-639: Document the X-Agent-Info attribution header - #1144

Merged
wkoutre merged 8 commits into
Uniswap:mainfrom
wkoutre:nickkoutrelakos/eco-639-x-agent-info-publish-in-dev-docs-llmstxt
Sep 2, 2026
Merged

ECO-639: Document the X-Agent-Info attribution header#1144
wkoutre merged 8 commits into
Uniswap:mainfrom
wkoutre:nickkoutrelakos/eco-639-x-agent-info-publish-in-dev-docs-llmstxt

Conversation

@wkoutre

@wkoutre wkoutre commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Linear: ECO-639

Summary

Adds a Swapping API page documenting X-Agent-Info, an optional request header that lets an integration declare its Trading API calls were made by an AI agent rather than a person, so agent-driven volume can be measured separately.

The new page content/trading/swapping-api/agent-attribution.mdx covers the three-field schema (decision_origin, integration_name, version), the rules that mark a header malformed, and a cURL plus TypeScript example. meta.json adds it to the nav after Integration Guide, and short cross-links point to it from integration-guide.mdx, common-errors.mdx, and uniswap-ai/overview.mdx.

Docs are the adoption mechanism here rather than an afterthought: the integrations this header exists to measure are largely built by agents, and an agent wiring up the Trading API reads the docs.

Type of change

  • New content (guide, page, code example)
  • Update to existing content

How has this been verified?

Checked the documented rules against the shipped parser, packages/lib/golang/middlewares/server/xagentinfo.go on Uniswap/backend main. Both worked examples parse clean against it. meta.json is valid JSON and every added link resolves to a real heading.

Anything else reviewers should know?

Not ready to merge. The page's "Confirming it was received" section documents an x-agent-info-status response header that does not exist: it lived in Uniswap/backend#11003, which was closed unmerged and superseded by #11941 (gateway parser only). git grep x-agent-info-status on backend main returns nothing. Several parse rules are also missing or stated in the wrong unit. Details are in the review comments on this PR.

Adds a dedicated page covering the optional X-Agent-Info request
header (decision_origin, integration_name, version), the rules that
mark it malformed, and the x-agent-info-status response contract used
to confirm it parsed. Cross-linked from the swapping API integration
guide, troubleshooting page, and the Uniswap AI overview.

ECO-639
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Thanks for contributing to Uniswap Docs! 🦄

Quick heads up on how this repo works: the docs you see on developers.uniswap.org are built from a separate source repo. When we accept a change here, we port it over there, so your fix might show up on the live site before it lands in this repo.

Updates to this repo come in batches on a regular cadence. When yours is included, you'll be credited as a co-author on the commit, so the contribution counts on your GitHub profile and in Uniswap/docs.

We review every PR but can't merge everything. Either way, someone from the docs team will follow up here once we've taken a look.

One legal bit: by submitting a PR you agree your contribution is licensed under the repo's MIT license.

msarkor718
msarkor718 previously approved these changes Aug 7, 2026

@wkoutre wkoutre left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed every documented rule against the shipped parser on Uniswap/backend main (f839c7996d) — packages/lib/golang/middlewares/server/xagentinfo.go and packages/services/entry-gateway-go/src/libs/apikeymetrics/emitter.go.

The headline: this page was written against Uniswap/backend#11003, which was closed unmerged on 2026-08-18. What actually shipped is #11941, a Go-only gateway parser with no response header and no TypeScript parser. So the request-header half of the page is largely accurate, and the response-contract half documents behavior that does not exist. Details in the inline comments.

What matched the parser

  • 1024-byte cap on the raw value, checked before parsing — matches (unit wording aside).
  • decision_origin must be exactly autonomous or human_mediated, case-sensitive — matches the literal constants.
  • Payload must be a JSON object; arrays, scalars and null are malformed — matches.
  • Unknown keys dropped rather than rejected — matches.
  • Optional fields present but not strings are malformed, including explicit null — matches, and the parser goes out of its way to make null distinguishable from absent.
  • Empty header value is absent, not malformed — matches.
  • A malformed header carries no payload and never echoes input — matches.
  • Both worked examples parse to ok.

What did not

Enumerated inline: the response header does not exist; the printable-US-ASCII rule, the authenticated-key gate, and the duplicate-header-line joining are all absent from the page; the 256 cap is UTF-16 code units rather than characters; the disallowed-character set is incomplete; free-text fields land in a warehouse with no guidance on what not to put in them.

llms.txtMedium, unanchorable. The ticket's "Done when" requires the content to reach llms.txt, and no such file exists in this repo. The PR description infers the private publishing pipeline regenerates it from content/ the way the archived Docusaurus setup did, which is plausible but unverified from here. Since the whole premise of the ticket is that agents discover this header through agent-readable indexes, "probably generated downstream" is a thin basis for calling the distribution requirement done. Worth one question to whoever owns that pipeline before this is marked ready — a confirmed yes closes the criterion, and a no means this PR does not actually satisfy the ticket.

House conventions — checked and clean, no findings. <Callout title="…" type="info"> matches existing usage (type="info" appears 37 times across content/), tables are normal in the Trading pages, the /docs/trading/... link prefix matches every neighbouring link, and frontmatter shape matches. The nav insertion in meta.json is valid JSON and sits sensibly after Integration Guide. Giving this its own page rather than folding it into the integration guide was the right call at this size.

Recommendation. Keep it in draft. The cleanest split is to ship the request-header half now — it is genuinely useful and mostly correct — with the parse rules corrected, and hold the response contract until a backend PR actually lands x-agent-info-status. That change currently has no owner and no open PR, which is worth surfacing on the project rather than leaving inside a docs ticket. I have updated ECO-639 to record the sequencing.

Not flagging anything as blocking beyond that; no REQUEST_CHANGES since the fix depends on a scope decision that is yours.

Comment thread content/trading/swapping-api/agent-attribution.mdx
Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment thread content/trading/swapping-api/common-errors.mdx Outdated
@wkoutre
wkoutre marked this pull request as ready for review September 1, 2026 18:31
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T18:34:55.939364Z 63ac4c5 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63ac4c52e9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread content/trading/swapping-api/agent-attribution.mdx Outdated
Comment on lines +21 to +22
| `integration_name` | string | No | Name of your integration or agent, e.g. `my-trading-bot`. Up to 256 characters. |
| `version` | string | No | Version identifier for your integration. Up to 256 characters. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document optional field limits in bytes

When integration_name or version contains multibyte UTF-8 characters, the backend parser applies the 256 limit to the encoded byte length, not the character count documented here. For example, a 200-character CJK name satisfies this table but is silently marked malformed, so specify a maximum of 256 UTF-8 bytes for both fields.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cap is 256 UTF-16 code units (String#length), not bytes: the parser's utf16Len counts units and its comment gives the counter-examples for a byte count. Fixed the table to say so. Your CJK example does get marked malformed, but by two other rules that are now documented: the raw header must be printable ASCII, and the whole raw value is capped at 1024 bytes, so 200 escaped CJK characters exceed it.

…avior

Adds the printable-ASCII, duplicate-header, and full disallowed-character rules to the malformed list. Corrects the field cap to UTF-16 code units and the size cap to raw bytes. Replaces the extra-keys guidance with an explicit rule against identifying values. Fixes the claim that x-agent-info-status only appears on successful responses; it also rides the gateway's own error responses. Makes the curl example's request body valid JSON.
wkoutre and others added 3 commits September 1, 2026 11:57
The header is set after routing and before any response is written, not before validation. Note that an unrouted 404 does not carry it.
…lippage

autoSlippage is optional in the published OpenAPI spec. The required set is
type, amount, tokenInChainId, tokenOutChainId, tokenIn, tokenOut, swapper.
@wkoutre
wkoutre requested a review from msarkor718 September 1, 2026 19:54
msarkor718
msarkor718 previously approved these changes Sep 1, 2026
…list is not Uniswap-only

The gateway writes no 404 of its own. Unknown paths fall through to the
default upstream, which answers after the status header is stamped, so a
404 carries it. Verified against production: GET /nope returns 404 with
x-agent-info-status: malformed. Only a CORS preflight skips it.

The CORS allow-list also holds a few partner domains, so "Uniswap
origins" overstated it.
@@ -0,0 +1,94 @@
---
title: Agent Attribution (X-Agent-Info)
description: Send the optional X-Agent-Info header to attribute agent-driven Trading API traffic, and check x-agent-info-status to confirm it was recognized.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frontmatter claims a response header that doesn't appear to exist. This description (and the "Confirming it was received" section below) documents an x-agent-info-status response header. Per the PR's own description, this header shipped in Uniswap/backend#11003, which was closed unmerged and superseded by #11941 (gateway parser only) — a git grep x-agent-info-status on backend main reportedly returns nothing. If that's accurate, this page documents a feature that was never shipped. Please confirm against the current backend implementation before merging (the PR author flagged this as "not ready to merge" for this exact reason).

Comment on lines +57 to +92

The gateway sets this header after it routes your request, and before it writes its own response. Error responses carry it too — a 400, a 401, a 403, a 429, and a 404 for a path that does not exist. So you can debug the header without first getting a working quote. A CORS preflight carries no status header, because the gateway answers it before it looks at `X-Agent-Info`.

Call the Trading API server-to-server. The gateway sends CORS headers only to a small allow-list of origins, so a browser page on your own domain cannot read this header. Check it from a server or with curl.

```typescript
const response = await fetch('https://trade-api.gateway.uniswap.org/v1/quote', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Agent-Info': JSON.stringify({
decision_origin: 'autonomous',
integration_name: 'my-trading-bot',
version: '1.4.0',
}),
},
body: JSON.stringify({
tokenIn: '0x...',
tokenOut: '0x...',
tokenInChainId: 1,
tokenOutChainId: 1,
type: 'EXACT_INPUT',
amount: '1000000',
swapper: '0x...',
slippageTolerance: 0.5,
}),
});

if (response.headers.get('x-agent-info-status') === 'malformed') {
// Received but dropped — check field names, decision_origin value, and length limits above.
console.warn('X-Agent-Info was sent but not recognized.');
}

const quote = await response.json();
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire section describes an x-agent-info-status response header, including exact values (malformed), timing guarantees relative to CORS preflight and error responses (400/401/403/429/404), and a worked TypeScript example that branches on it. If this header was never actually shipped (see comment on the frontmatter above — the PR description says the implementing PR was closed unmerged and superseded by a gateway-only parser), this whole section is fabricated behavior that will mislead integrators: they'll write code that checks a header that never arrives and silently always treat requests as unattributed. This should be corrected or removed before merge, not just flagged in the PR description.

Comment on lines +47 to +53
- `integration_name` or `version` contains a disallowed character. Those are control characters (C0 `0x00`–`0x1F`, DEL `0x7F`, C1 `0x80`–`0x9F`), the Unicode line separators U+2028 and U+2029, and the replacement character U+FFFD. An escaped unpaired surrogate decodes to U+FFFD, so it is rejected too. Text pasted from a PDF, or left behind by a lossy re-encode, often carries one of these invisibly.

Not sending the header at all, or sending it with an empty value, isn't an error condition — both are simply "no attribution," the same outcome as a header that's out of scope for your client.

## Confirming it was received

Because the request succeeds regardless of whether `X-Agent-Info` parsed, check the `x-agent-info-status` response header to confirm your header was actually recognized:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the PR description, some of these parse rules ("Several parse rules are also missing or stated in the wrong unit") may not match the shipped parser (xagentinfo.go in Uniswap/backend). I can't independently check that private repo from here, so I can't confirm which specific bullet(s) are off (e.g. the 1024-byte cap, the ASCII range check, the 256 UTF-16 code unit limit, or the disallowed-character set). Someone with access to the current backend implementation should diff each bullet against the real validation logic before merging — a wrong limit here (e.g. byte cap vs. character cap) would cause integrators' otherwise-valid headers to silently fail attribution.


The API is specific about request header validation. In particular, ensure that your `accept` and `content-type` headers only include the value `application/json`. For a complete example of properly formatted request headers, see the authentication section of the [Developer Dashboard](https://developers.uniswap.org/dashboard).

If you're sending the optional `X-Agent-Info` attribution header and it isn't being picked up, check the response for an `x-agent-info-status: malformed` header — see [Agent Attribution](/docs/trading/swapping-api/agent-attribution#confirming-it-was-received) for the full set of rules that make the header malformed. A malformed `X-Agent-Info` never causes an error response; the request proceeds without attribution.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also references the x-agent-info-status: malformed response header. If that header turns out not to exist in production (see the flag on agent-attribution.mdx), this troubleshooting tip should be reverted along with the new page.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review summary

Do not merge as-is — the PR author's own description flags this as not ready, and the diff confirms why.

Main issue (high confidence, self-flagged by the author): The new agent-attribution.mdx page's "Confirming it was received" section, its frontmatter description, and the added line in common-errors.mdx all document an x-agent-info-status response header. Per the PR description, the PR that would have shipped this header (Uniswap/backend#11003) was closed unmerged and superseded by a gateway-only parser (#11941) that doesn't set this header. If accurate, this documents a feature that never shipped — integrators following this doc will write code checking a header that never arrives. Left inline comments on the specific sections.

Secondary issue: The author also notes some of the parse rules in "What makes a header malformed" (byte cap, ASCII range, UTF-16 length limit, disallowed characters) may be missing or stated in the wrong unit relative to the actual xagentinfo.go parser. I don't have access to Uniswap/backend to verify which ones — flagged inline for someone who does.

Everything else checked out fine:

  • meta.json is valid JSON, and agent-attribution is correctly placed in the nav.
  • All internal links/anchors resolve: integration-guide#authentication, agent-attribution#confirming-it-was-received, and the incidental fix from #install-as-a-claude-code-plugin to #claude-code-marketplace in uniswap-ai/overview.mdx (the old anchor was broken; the new one matches the actual heading).
  • The rewritten "required fields" list in common-errors.mdx (type, amount, tokenInChainId, tokenOutChainId, tokenIn, tokenOut, swapper) matches the request body shown in integration-guide.mdx's basic quote example.
  • No contract addresses or chain IDs changed. No suspicious/promotional content. Prose, formatting, and MDX syntax are all clean.

Once the x-agent-info-status content is corrected/removed (or confirmed to actually exist) and the parse rules are verified against the real parser, this should be good to merge.

@wkoutre
wkoutre requested a review from msarkor718 September 2, 2026 00:49
@wkoutre
wkoutre merged commit 1c7597d into Uniswap:main Sep 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants