Agent Guide
This guide is for developers building AI purchasing agents, procurement bots, or automated buying systems that consume procurement.txt files.
Discovery
An agent should follow this discovery sequence:
- Request
https://{domain}/procurement.txtvia HTTPS. - If the response is 404, try
https://{domain}/.well-known/procurement.txt. - If neither location returns a 200 response, assume no
procurement.txtexists. Do not infer procurement capabilities.
async function discover(domain) {
const primary = await fetch(`https://${domain}/procurement.txt`)
if (primary.ok) return primary.text()
if (primary.status === 404) {
const fallback = await fetch(`https://${domain}/.well-known/procurement.txt`)
if (fallback.ok) return fallback.text()
}
return null // No procurement.txt found
}Parsing
Parse the file line by line:
- Strip any UTF-8 BOM at the start of the file.
- Ignore blank lines (empty or whitespace-only).
- Ignore comment lines (starting with
#). - Split each directive on the first occurrence of
:(colon + space). - The text before
:is the field name; the text after is the value. - Normalize field names to lowercase for matching.
function parse(text) {
const fields = {}
const errors = []
// Strip BOM
if (text.startsWith('\uFEFF')) text = text.slice(1)
for (const [i, line] of text.split(/\r?\n/).entries()) {
if (!line.trim() || line.trimStart().startsWith('#')) continue
const sep = line.indexOf(': ')
if (sep === -1) {
errors.push({ line: i + 1, message: `Missing ': ' separator` })
continue
}
const key = line.slice(0, sep).toLowerCase()
const value = line.slice(sep + 2)
// Repeatable fields
if (key === 'contact' || key === 'escalation' || key === 'commerce-protocol') {
fields[key] = [...(fields[key] || []), value]
} else {
fields[key] = value // last value wins for duplicates
}
}
return { fields, errors }
}Handling API URIs
When a file declares Ordering: api <URI>, Pricing: api <URI>, or any capability field with api <URI> (e.g., Quote: api <URI>, Tracking: api <URI>), the URI points to a machine-readable API specification or structured documentation — not a raw endpoint.
What to expect at the URI:
- An OpenAPI 3.x specification (JSON or YAML) — the preferred format
- A structured API documentation page
- Another machine-readable description (e.g., Postman collection)
How to handle it:
- Fetch the URI.
- Detect the format (check
Content-Type, or look for OpenAPI signature fields likeopenapi,info,paths). - Parse the specification to determine available endpoints, request schemas, and response formats.
- Read the spec's
securitySchemesandsecurityfields to understand authentication requirements. Credentials are pre-established out-of-band — the spec tells you what method is required, not how to acquire credentials on-the-fly.
Merchants will often use the same OpenAPI spec URI across multiple fields (Ordering, Pricing, Quote, etc.). This is expected — fetch the spec once and use the relevant paths for each operation.
If the URI does not return a recognizable API specification, treat the field as if it were on-request and fall back to Contact or Escalation.
Unknown Fields
Agents MUST ignore fields they do not recognize. This includes:
- Fields beginning with
X-(extension fields) - Any field name not in the v1 field list
- Fields from future versions
Do not treat unknown fields as errors. This ensures forward compatibility.
Missing Fields
If an optional field is absent, do not assume a default behavior.
- No
Negotiationfield does not mean negotiation is available or unavailable — it is unknown. - No individual capability field (e.g. no
Quotefield) means that capability has not been declared — it may or may not be available. - No
Rate-Limitfield: default to conservative rates (≤1 request/second).
Caching
- Respect standard HTTP caching headers (
Cache-Control,ETag,Last-Modified). - If an
Expiresfield is present in the file body, re-fetch after that date regardless of HTTP cache headers. - If a file is expired (
Expiresis in the past), treat the data as stale and re-fetch if possible.
Human Escalation
When handing off a transaction to a human:
- Prefer
EscalationURIs overContactURIs — they are intended for active transaction hand-offs. - If no
Escalationfield exists, fall back toContact. - Support multiple contact/escalation entries — try them in order or present the best match.
Rate Limits
- Respect the advisory
Rate-Limitfield. - If absent, default to no more than 1 request per second to the merchant's purchasing endpoints.
- The field is advisory, not enforced. Being a good citizen protects you from being blocked.
Security Considerations
Fetch over HTTPS only
Agents MUST only fetch procurement.txt over HTTPS. Do not follow redirects from the https:// URL to http://.
Don't treat the file as proof of identity
procurement.txt is unsigned and unauthenticated. Verify:
- The file is served over HTTPS.
- The domain matches the merchant you intend to interact with.
- The
Locationheader of any redirect stays on the same domain.
Do not follow redirects from procurement.txt URLs to a different domain without flagging the redirect to the user.
Cross-domain URIs
The Contact, Escalation, Catalog, and API specification URIs may point to different domains (e.g., a CDN-hosted OpenAPI spec, or a third-party documentation platform). Flag cross-domain API URIs for human review before executing transactions, unless your agent has been explicitly configured to trust cross-domain references for that merchant.
Don't auto-execute purchases
Agents MUST NOT auto-execute financial transactions — purchases, payments, or commitments — based solely on procurement.txt without human authorization or a pre-configured trust policy.
This file describes capabilities and declares intent. All transactions remain subject to the merchant's terms of service, applicable law, and any required human authorization.
Canonical-Hash verification
If the Canonical-Hash field is present, verify it:
- Remove the
Canonical-Hashline from the fetched content. - Normalize line endings to
LF. - Compute SHA-256 of the remaining content.
- Compare the hex digest with the declared value (the part after
sha256:).
Treat a mismatch as a warning, not a hard failure. The merchant may have updated the file without yet recomputing the hash. A match gives additional confidence that the file has not been altered since publication.
async function verifyCanonicalHash(rawText, declaredHash) {
if (!declaredHash.startsWith('sha256:')) return 'unsupported-algorithm'
const expected = declaredHash.slice(7)
// Remove the canonical-hash line and normalize line endings
const stripped = rawText
.split(/\r?\n/)
.filter(line => !line.toLowerCase().startsWith('canonical-hash:'))
.join('\n')
const encoded = new TextEncoder().encode(stripped)
const hashBuffer = await crypto.subtle.digest('SHA-256', encoded)
const computed = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
return computed === expected ? 'match' : 'mismatch'
}Handling Commerce-Protocol Fields
When a Commerce-Protocol field is present:
- Prefer the protocol for supported transaction types. UCP and ACP handle one-shot purchases with integrated payment. For these transactions, use the declared endpoint directly rather than the
Ordering: apipath. - Ignore unrecognized protocol names. If you encounter
Commerce-Protocol: xyz https://…and don't supportxyz, ignore that line and fall back to other declared ordering methods. - Both declared is fine. If a merchant declares both
Commerce-ProtocolandOrdering: api, use whichever suits the transaction: the protocol for fast consumer-style purchases, the API for complex B2B orders. Ordering: protocolwithout aCommerce-Protocolfield means the ordering method is unknown — fall back toContact.
Using Interaction-Model
The Interaction-Model field tells you upfront how much human involvement to expect:
| Value | What it means for your agent |
|---|---|
automated | You can complete the transaction end-to-end. No approval step needed. |
approval-required | Prepare and submit the order, then wait for human authorization before it is fulfilled. |
human-led | Use the file for discovery only. Surface findings to a human who will initiate the transaction. |
hybrid | Check X-Auto-Approve-Under (if present) to determine whether this specific order qualifies for automated approval. |
If Interaction-Model is absent, treat the interaction model as unknown — do not assume automation is permitted.
Agent Readiness Scores
Use the following heuristic to score how ready a merchant's setup is for automated procurement:
| Score | Criteria |
|---|---|
| HIGH+ | HIGH + at least two of: Service-Region, Payment-Terms, Auth, Min-Order, Interaction-Model |
| HIGH | Valid file + (Ordering + Pricing) or Commerce-Protocol |
| MEDIUM | Valid file + Ordering and/or Pricing |
| LOW | Valid file with only Version and Contact |
| NONE | File is invalid or missing |
A HIGH score means the merchant has declared enough for an agent to take action — either Ordering + Pricing together, or a Commerce-Protocol endpoint (which subsumes both by providing a complete transactional flow including payment). Individual capability fields (Quote, Invoice, Tracking, etc.) provide additional detail but are not required for HIGH.
A HIGH+ score indicates the merchant has additionally provided key context. Interaction-Model now counts toward HIGH+ alongside Service-Region, Payment-Terms, Auth, and Min-Order.
A LOW score means the file is valid but provides minimal guidance.
Using the Validator API
You can use the procurement.txt validator API to check any domain:
GET https://procurementtxt.org/api/validate?domain=example.com
Response:
{
"domain": "example.com",
"url": "https://example.com/procurement.txt",
"valid": true,
"fields": {
"version": "1",
"contact": ["mailto:sales@example.com"],
"ordering": "api https://api.example.com/openapi.json",
"pricing": "public",
"quote": "yes",
"invoice": "api https://api.example.com/openapi.json"
},
"warnings": [],
"errors": [],
"agentReadiness": "HIGH",
"fetchedAt": "2026-03-23T00:00:00Z"
}Plain text format:
GET https://procurementtxt.org/api/validate?domain=example.com&format=text