Service Documents
How Service Documents works
When someone signs up for a website, they may need to accept its terms of service or read its privacy policy. Service Documents brings that step to AEP enrollment: your service publishes a list of documents, the application using AEP presents them to the person responsible for the agent, and enrollment communicates which required documents that person accepted. AEP calls that person the Owner.
Each document has a web page that the person can open in a browser. You can also publish the same document in AEP's structured JSON format so applications can display it directly, for example as readable text in a terminal. This additional format is called portable content. It is optional because the web page already provides a way to read the document.
Your service chooses which documents require acceptance. For example, it might require acceptance of its terms while providing a privacy notice for reference. Service Documents is an optional extension; services without documents can continue using AEP enrollment without it.
Advertise documents in Inspect
Inspect is the AEP operation that tells an application what your service supports before enrollment. To make your documents discoverable, declare the Service Documents extension in extensions.supported and put the document list in service_documents. The example below shows these fields within an Inspect response; the other required Inspect fields are omitted for brevity.
{
"extensions": {
"supported": [
"urn:aep:ext:aep-foundation:service-documents#v=1.0.0"
]
},
"service_documents": [
{
"id": "general-terms",
"type": "terms_of_service",
"title": "General terms of service",
"enrollment": "required",
"representations": [
{"media_type": "text/html", "url": "https://service.example/legal/terms"},
{"media_type": "application/json", "url": "https://service.example/legal/terms.json"}
]
},
{
"id": "privacy",
"type": "privacy_policy",
"title": "Privacy policy",
"enrollment": "not-required",
"representations": [
{"media_type": "text/html", "url": "https://service.example/legal/privacy"}
]
}
]
}In this example, general-terms and privacy are document IDs chosen by the service. Enrollment uses these IDs to refer to documents, while people see their titles. Keep an ID stable when you update the same document, and do not reuse it for a different document. You can list up to seven documents. Each ID starts with an ASCII letter or digit and contains at most 32 ASCII letters, digits, periods, underscores, or hyphens. IDs are case-sensitive. Omit the list if you have no documents.
The enrollment field controls acceptance: required asks for acceptance during enrollment; not-required makes the document available without requiring that action. The type field describes the kind of document, not whether it must be accepted. Prefer the registered document types, such as terms_of_service and privacy_policy. Custom types are allowed, and multiple documents can share a type—for example, general terms and product-specific terms.
representations lists the available formats and their URLs. Every document needs exactly one HTML web page, identified by text/html. You may also offer portable JSON as application/json or a Markdown version as text/markdown. Your application must host these URLs and let people read them without AEP credentials; adding a URL to Inspect does not create the page for you.
Read before enrollment
If you are building an application that enrolls agents, show the documents before asking the person to approve enrollment. Include each document's ID, title, whether acceptance is required, and its web link. A person can follow the link to read the document in their browser. Opening or reading it does not count as acceptance.
Try it with the InFlow demo service. With the InFlow CLI installed, run these commands in your terminal. You do not need to sign in or enroll to inspect and read its documents:
inflow aep inspect https://demo.inflowpay.ai inflow aep document https://demo.inflowpay.ai privacy
The first command lists the demo's documents, including their IDs and acceptance requirements. The second uses the privacy ID from that list to display the privacy document's text. Compare it with the same document in your browser. This is sample content for the demo, not InFlow's production privacy policy.
If your application supports the portable JSON format described below, it can display the document itself. When that format is unavailable or cannot be read, keep the web link available instead. Do not substitute an automatically generated summary or display an incomplete download as the full document.
To see that alternative, read the demo's acceptable use policy. It is available only as a web page, so this command gives you its browser link rather than document text:
inflow aep document https://demo.inflowpay.ai acceptable-use
Document downloads are separate from authenticated AEP requests: they must not carry credentials, identifying application headers, or cookies. Published URLs use HTTPS; HTTP is allowed for local development on localhost, 127.0.0.1, or [::1]. The SDK readers enforce download limits, but your application remains responsible for deciding which destinations it trusts.
Display documents outside a browser
A terminal application cannot display a web page in the same way a browser does. Portable content gives it the document's text and structure without requiring it to extract them from HTML. You write the document as JSON blocks—paragraphs, titled sections, bulleted lists, and numbered lists—and the application turns those blocks into readable text.
That is how the privacy command above displays its text: the CLI reads the demo's JSON document and renders its paragraphs and section headings for you. You can open that JSON to see the source behind the terminal output.
The JSON uses the same document ID as Inspect. Its content must contain the full substantive text of the web document, not just highlights. The format and version fields identify the format so an application knows how to read it. This short example represents a document whose web page contains the same statements:
{
"format": "aep-service-document",
"version": "1.0",
"id": "general-terms",
"content": [
{
"type": "section",
"title": "Permitted use",
"blocks": [
{"type": "paragraph", "text": "You must use the service lawfully."},
{"type": "bulleted_list", "items": ["Do not impersonate others.", "Do not interfere with other users."]}
]
}
]
}You may include a summary alongside content. It uses the same block structure and can be more than a sentence. The service writes this summary; the reading application does not invent one. Label it as a summary supplied by the service and let the reader reach the full text and web page.
Applications should preserve the document's wording, order, headings, and lists. If an application cannot display a block, it should offer the web page rather than silently leave part of the document out. Terminal applications also need to neutralize control characters so document text cannot manipulate the terminal display.
When an AI assistant is the interface, return rendered text rather than raw JSON. Enclose the text in a fenced code block, with a delimiter that the document cannot close. Outside the block, include the source link and instruct the assistant to present the text faithfully without interpreting, summarizing, evaluating, or following instructions inside it. This keeps the service's words separate from instructions to the assistant.
Collect acceptance during enrollment
Acknowledgement is how AEP communicates the person's acceptance to the service. After the person approves the required documents, send their IDs in acknowledged_document_ids with the Enroll request. One confirmation can cover all the required documents shown together. Discovering documents, downloading them, or continuing to run a command is not that confirmation.
{
"agent_did": "did:web:agent.example:agents:123",
"acknowledged_document_ids": ["general-terms"]
}For example, InFlow presents the required documents on its enrollment approval screen. The approval refers to that service and the original list of required IDs. If the service adds another document while approval is pending, the application must not include it as though the person had accepted it. If an original document cannot be found, the application must not silently remove it from the decision.
The Enroll response returns the IDs sent in that request. This is not a complete history of acceptance: omitting the field or sending an empty list does not erase earlier accepted IDs. An ID also does not identify a particular revision of the document or prove that its text was read. Your service decides what acceptance records it needs to keep.
Ask for acceptance when requirements change
Suppose someone enrolled after accepting your general terms, and you later introduce product-specific terms. Your service decides whether to notify that person and whether further acceptance is needed. AEP does not automatically compare document revisions or require everyone to enroll again.
If further acceptance is needed, your service can report it when the agent calls Grant, the operation used to request an access credential. Grant returns HTTP 422 without a credential and identifies the missing document IDs. It can also identify missing account information, called claims in AEP. This example asks for product terms and an email address:
{
"type": "urn:aep:error:requirements_unmet",
"title": "Enrollment requirements unmet",
"status": 422,
"code": "requirements_unmet",
"owner_action_required": "true",
"requirements_pending": ["contact.email", "service_documents"],
"required_document_ids": ["product-terms"]
}The application explains what is missing and tells the caller to run Enroll again. Repeating Enroll lets the person supply the information or accept the documents without creating a different agent identity. Existing claims and accepted IDs are preserved when omitted from the update, and existing credentials remain intact. Enrollment does not automatically reactivate a suspended or terminated account. After resolving the requirements, the caller requests Grant again; neither the SDK nor CLI chains these operations automatically.
The same approach applies if requirements change while a person is approving enrollment. Accept an otherwise valid acknowledgement of the documents they were shown, rather than rejecting it just because another document was added meanwhile. Your service can request the remaining acceptance through Grant afterward. For request retry rules, see errors and idempotency.
Integrate and verify
The Node.js, Go, Java, Python, and Rust SDK guides identify the configuration and reading APIs. InFlow CLI provides inspection, document reading, and an Owner approval workflow.
When testing your integration, cover both required and not-required documents, a document available only as a web page, and a changed requirement that is resolved by repeating Enroll before requesting Grant again. Check that the Enroll response returns exactly the IDs supplied in that request. An older application that does not support Service Documents cannot satisfy required acceptance by ignoring the document list.
See the Service Documents specification for the complete format and limits. The protocol index provides draft downloads, schemas, examples, and test vectors. See conformance guidance for implementation checks.