33[ ![ CI] ( https://github.com/offering-protocol/odp-python/actions/workflows/ci.yml/badge.svg )] ( https://github.com/offering-protocol/odp-python/actions/workflows/ci.yml )
44[ ![ Python] ( https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white )] ( https://www.python.org/ )
55[ ![ PyPI] ( https://img.shields.io/pypi/v/offering-protocol )] ( https://pypi.org/project/offering-protocol/ )
6+ [ ![ Codecov] ( https://codecov.io/gh/offering-protocol/odp-python/graph/badge.svg )] ( https://codecov.io/gh/offering-protocol/odp-python )
67[ ![ License: MIT] ( https://img.shields.io/badge/License-MIT-yellow.svg )] ( ./LICENSE )
78
89Official Python software development kit for the
910[ Offering Discovery Protocol] ( https://www.offeringprotocol.org/ ) , the open protocol for discovering
1011Services and navigating their Offerings.
1112
12- ODP separates Service discovery from catalog discovery. An Agent searches the canonical Directory
13- for candidate Services, inspects each Service's live ODP document, and then navigates or searches
14- that Service's Collections and Offerings.
13+ ODP separates two levels of discovery:
14+
15+ 1 . An Agent searches the canonical Directory for Services.
16+ 2 . The Agent inspects a Service's live ODP document and navigates that Service's Collections and
17+ Offerings.
18+
19+ The Directory does not copy every Service catalog. Catalog searches go directly to each Service.
1520
1621## Installation
1722
1823``` sh
1924python -m pip install offering-protocol
2025```
2126
22- The distribution provides one typed Python package with modules for each integration role:
27+ Python 3.11 or newer is required. The distribution provides one typed package with modules for each
28+ integration role:
29+
30+ | Goal | Module |
31+ | --- | --- |
32+ | Parse protocol models and validate normative documents | ` offering_protocol.core ` |
33+ | Search the canonical production or sandbox Directory | ` offering_protocol.directory ` |
34+ | Inspect Services and navigate their catalogs | ` offering_protocol.agent ` |
35+ | Publish an ODP Service | ` offering_protocol.service ` |
36+
37+ ## Search the Directory
38+
39+ ` DirectoryClient ` uses the one canonical production Directory. Select ` Environment.SANDBOX ` when
40+ working against InFlow's sandbox.
41+
42+ ``` python
43+ import asyncio
44+
45+ from offering_protocol.directory import DirectoryClient, SearchRequest, ServiceFilters
46+
47+
48+ async def main () -> None :
49+ async with DirectoryClient() as directory:
50+ page = await directory.search(
51+ SearchRequest(
52+ query = " indoor plants" ,
53+ filters = ServiceFilters(keywords = [" plants" ]),
54+ limit = 20 ,
55+ )
56+ )
57+ for service in page.items:
58+ print (service.name, service.service_origin)
59+
60+ if page.next:
61+ next_page = await directory.continue_search(page.next)
62+ print (f " Next page contains { len (next_page.items)} Services " )
63+
64+
65+ asyncio.run(main())
66+ ```
67+
68+ Use ` search_services() ` when the application wants bounded automatic pagination. Use ` suggest() ` to
69+ discover keyword completions supported by the Directory.
70+
71+ ## Inspect and navigate a Service
72+
73+ ` ServiceClient ` checks the Service document before calling an operation. Calling an operation the
74+ Service does not advertise raises ` UnsupportedOperationError ` before a catalog request is sent.
75+
76+ ``` python
77+ import asyncio
78+
79+ from offering_protocol.agent import ServiceClient
80+ from offering_protocol.core import OfferingSearchRequest, Representation
81+
82+
83+ async def main () -> None :
84+ async with ServiceClient(" https://demo.inflowpay.ai" ) as service:
85+ inspection = await service.inspect()
86+ print (inspection.document.name)
87+ print ([operation.name.value for operation in inspection.document.operations])
88+
89+ page = await service.search_offerings(
90+ OfferingSearchRequest(query = " plant" ),
91+ Representation.TERSE ,
92+ )
93+ for offering in page.items:
94+ print (offering.id, offering.name, offering.price)
95+
96+ details = await service.get_offering_details(page.items[0 ].id)
97+ for action in details.actions:
98+ print (action.id, action.rel.value, action.authentication.value)
99+
100+
101+ asyncio.run(main())
102+ ```
103+
104+ ` get_offering_details() ` resolves and validates an Offering's Attribute Schema, normalizes usable
105+ Actions, and reports non-fatal issues separately from the Offering. ` resolve_action() ` resolves a
106+ specific Action's HTTP or OpenAPI target and request schema. It never calls the target, enrolls,
107+ authenticates, or pays.
108+
109+ The Agent module also provides:
110+
111+ - Collection list, get, search, and bounded traversal operations.
112+ - Offering list, get, search, collection listing, continuation, and bounded traversal operations.
113+ - Effective inline and linked Filter and Sort definitions for Service and Collection scopes.
114+ - Directory-to-Service federated Offering discovery through ` Agent ` .
115+ - Conditional request and representation caching with injectable ` Cache ` and ` Transport ` protocols.
116+
117+ Default fallback cache lifetimes are four hours for Service documents, one hour for Collections,
118+ and five minutes for Offerings. HTTP cache directives take precedence. Provide distinct ` transport `
119+ and ` supporting_transport ` instances when protocol resources and linked schemas require different
120+ credentials or network policy.
121+
122+ ## Publish a Service
123+
124+ ` Service ` is framework-neutral. Adapt the incoming framework request to ` Request ` , call
125+ ` Service.handle() ` , and copy the returned status, headers, and body into the framework response.
126+
127+ ``` python
128+ from offering_protocol.core import Collection, Offering
129+ from offering_protocol.service import ServiceBuilder, StaticCatalog, StaticCatalogOptions
130+
131+ catalog = StaticCatalog(
132+ StaticCatalogOptions(
133+ collections = (Collection(id = " plants" , name = " Plants" , odp_version = " 1.0" ),),
134+ offerings = (
135+ Offering(
136+ collection_ids = [" plants" ],
137+ description = " A resilient indoor plant." ,
138+ id = " rubber-plant" ,
139+ name = " Rubber Plant" ,
140+ odp_version = " 1.0" ,
141+ ),
142+ ),
143+ )
144+ )
145+
146+ service = (
147+ ServiceBuilder(
148+ name = " Indica Flowers" ,
149+ description = " An AI-enabled store for houseplants and plant care." ,
150+ language = " en" ,
151+ endpoint_base = " /odp" ,
152+ )
153+ .keywords([" houseplants" , " indoor-plants" ])
154+ .website_url(" https://example.com" )
155+ .build(catalog)
156+ )
157+ ```
158+
159+ Every Service integration must implement ` list-offerings ` and ` get-offering ` . ` StaticCatalog ` is the
160+ small-Service implementation: it adds Collection operations when Collections are provided and uses
161+ stateless, expiring continuations. Larger Services can implement the typed ` Catalog ` protocol over
162+ their existing indexed catalog and search infrastructure.
23163
24- | Goal | Module |
25- | --------------------------------------------- | ----------------------------- |
26- | Work with protocol models and validation | ` offering_protocol.core ` |
27- | Search the canonical Directory | ` offering_protocol.directory ` |
28- | Discover Services and navigate their catalogs | ` offering_protocol.agent ` |
29- | Publish an ODP Service | ` offering_protocol.service ` |
164+ Service responses are validated against the bundled normative schemas before they are returned.
165+ The handler enforces fixed operation paths and methods, ODP media types, request and response byte
166+ limits, local identifiers, page limits, and protocol Problem Details.
30167
31- The dependency direction remains narrow: Core is transport-independent, Directory depends toward
32- Core, Agent composes Core and Directory, and Service depends toward Core without depending on Agent
33- behavior.
168+ See [ examples/README.md] ( ./examples/README.md ) for a runnable Service and Agent.
34169
35170## Protocol composition
36171
37- ODP discovers what a Service offers and how an Agent can act on an Offering. A Service Document and
172+ ODP discovers what a Service offers and how an Agent can act on an Offering. A Service document and
38173its Actions can advertise AEP enrollment and MPP or x402 payment requirements, but ODP does not
39174create credentials, invoke Actions, or submit payments. Applications compose the appropriate
40- enrollment and payment clients around an Action resolved through ODP.
175+ enrollment and payment client around an Action resolved through ODP.
176+
177+ ## Errors and validation
178+
179+ Each role exposes typed errors:
180+
181+ - ` OdpValidationError ` includes deterministic schema and semantic issues.
182+ - ` DirectoryError ` and ` DirectoryRequestError ` describe canonical Directory failures.
183+ - ` AgentError ` , ` ServiceRequestError ` , and ` UnsupportedOperationError ` describe Agent-side failures.
184+ - ` ServiceError ` , ` CatalogError ` , and ` RequestError ` describe Service integration failures.
185+
186+ Protocol models preserve additive members in ` model.additional ` and round-trip them through
187+ ` model.to_dict() ` . Parsing remains strict for normative constraints and fields that prohibit unknown
188+ members.
41189
42190## Development
43191
44- Python 3.11 or newer and [ uv] ( https://docs.astral.sh/uv/ ) are required. Install the locked development
45- environment and run the complete merge gate with:
192+ Python 3.11 or newer and [ uv] ( https://docs.astral.sh/uv/ ) are required.
46193
47194``` sh
48195make sync
@@ -55,8 +202,9 @@ Format source files with:
55202make format
56203```
57204
58- The merge gate checks formatting, linting, strict type checking, branch coverage, distribution
59- metadata, and installation of the built wheel into a clean virtual environment.
205+ The merge gate checks formatting, linting, strict type checking, 100 percent line and branch
206+ coverage, distribution metadata, bundled runtime schemas, and installation of the built wheel into
207+ a clean virtual environment.
60208
61209See [ ` odp-specs ` ] ( https://github.com/offering-protocol/odp-specs ) for the normative draft, schemas,
62210examples, and test vectors.
0 commit comments