opedd
Official Python SDK for the Opedd content licensing API (buyer-side).
Opedd is programmatic licensing infrastructure between AI buyers and publishers — rights, usage tracking, and payment. "Stripe for content licensing."
Install
pip install opedd
Requires Python 3.10+.
Quickstart
from opedd import Opedd
client = Opedd(buyer_token="opedd_buyer_live_...")
# Fetch a single licensed article
article = client.content.get("article-uuid")
print(article["title"], article["author"], article["word_count"])
# Stream the full licensed catalog as NDJSON
client = Opedd(access_key="ent_xxx", buyer_email="eng@yourlab.com")
for row in client.feed.stream_ndjson(limit=5000):
train_model.ingest(row["content"], metadata=row)
# Pull a procurement-defense compliance dossier
client = Opedd(buyer_jwt="eyJhbGc...")
dossier = client.compliance.report(from_="2026-04-01", to="2026-04-30")
print(f"Retrievals: {dossier['dossier_metadata']['summary']['total_retrievals']}")
Phase 12 Wave 1 + 3 surfaces (0.2.0)
# Catalog scoping — discover what a publisher licenses (public, no auth needed for the call)
manifest = client.rsl.get(publisher_id="8268c353-ffa3-4db3-bbb2-90ddbbb43e41")
for license_type, terms in manifest["permits"].items():
if terms["permitted"]:
print(f"{license_type}: {terms['endpoint']}")
# CDSM Article 4(3) provenance — signed JSON-LD receipt for EU regulator verification
signed = client.rsl.get(publisher_id="8268c353-...", jsonld=True)
assert signed["opedd:legalBasis"].startswith("EU CDSM")
print("HMAC signature:", signed["opedd:signature"]["signature"])
# EU AI Act Article 53 attestation — buyer-side regulator-facing JWT
client = Opedd(buyer_jwt="eyJhbGc...")
attestation = client.compliance.article_53_attestation(
license_id="11111111-2222-3333-4444-555555555555",
window_start="2026-02-22T00:00:00Z",
window_end="2026-05-22T00:00:00Z",
)
jwt_token = attestation["data"]["jwt"] # Hand to legal/procurement audit committee
claims = attestation["data"]["claims"]
print(f"Events in window: {claims['usage']['events_in_window']}")
print(f"Tempo Merkle root: {claims['tempo']['merkle_root']}")
# Programmatic publisher onboarding — detect platform behind a URL
detection = client.onboarding.detect_platform(url="https://noahpinion.substack.com")
data = detection["data"]
if data["confidence"] == "high":
print(f"Platform: {data['platform']}, archive via {data['archive_method']}")
else:
print(data["instructions"]) # human-readable operator copy
# Buyer-discovery catalog browse + URL → licensable check + license verification
directory = client.discovery.publisher_directory(category="finance", min_articles=5, limit=20)
for pub in directory["data"]["publishers"]:
print(pub["name"], pub["article_count"], pub["pricing"])
try:
hit = client.discovery.lookup_article(url="https://publisher.com/articles/...")
print(f"Licensable: ${hit['data']['ai_price']} per article")
except OpeddNotFoundError:
print("Not in Opedd registry")
check = client.discovery.verify_license(key="OP-XXXX-XXXX")
print(f"Blockchain status: {check['data']['blockchain_status']}")
For end-to-end walkthroughs, see the cookbook.
Credentials
The SDK supports three credential types depending on which endpoint you call:
| Endpoint | Credential | Construction |
|---|---|---|
client.content.get(...) |
Bearer buyer token | Opedd(buyer_token="opedd_buyer_live_...") |
client.feed.list(...) / client.feed.stream_ndjson(...) |
Access key | Opedd(access_key="ent_...") — full text in the feed for AI training orders only (content_access="included"); other orders are discovery-only (content_body=None): fetch articles via client.content.get(...) (pay-per-request returns a snippet) |
client.audit.events(...) |
Supabase JWT | Opedd(buyer_jwt="eyJhbGc...") |
client.compliance.report(...) |
Supabase JWT | Opedd(buyer_jwt="eyJhbGc...") |
client.compliance.article_53_attestation(...) |
Supabase JWT | Opedd(buyer_jwt="eyJhbGc...") |
client.licenses.order(...) |
Buyer session JWT | Opedd(buyer_jwt="eyJhbGc...") |
client.licenses.orders(...) / client.licenses.get_order(...) |
Audit-scoped buyer API key or JWT | Opedd(buyer_token="opedd_buyer_live_...") |
client.licenses.list() |
Supabase JWT | Opedd(buyer_jwt="eyJhbGc...") |
client.rsl.get(...) |
None (public) | Any (constructor still requires one) |
client.onboarding.detect_platform(...) |
None (public) | Any (constructor still requires one) |
client.discovery.publisher_directory(...) |
None (public) | Any (constructor still requires one) |
client.discovery.lookup_article(...) |
None (public) | Any (constructor still requires one) |
client.discovery.verify_license(...) |
None (public) | Any (constructor still requires one) |
Multiple credentials can be supplied at once and the SDK selects the correct one per endpoint.
Env-var fallbacks
The constructor reads from these env vars when arguments are omitted:
OPEDD_BUYER_TOKENOPEDD_BUYER_JWTOPEDD_ACCESS_KEYOPEDD_BASE_URL(defaulthttps://api.opedd.com)
Exchanging an access key for a bearer token
client = Opedd.from_access_key(
access_key="ent_xyz...",
buyer_email="eng@yourlab.com",
)
# client.buyer_token is now set; you can call /content-delivery
API surface
client.content.get(article_id)
client.feed.list(since=None, cursor=None, limit=200)
client.feed.stream_ndjson(since=None, cursor=None, limit=5000) # generator
client.audit.events(from_=None, to=None, event_type=None, cursor=None, limit=100)
client.compliance.report(from_, to, cursor=None)
client.compliance.article_53_attestation(
license_id, content_id=None, window_start=None, window_end=None,
) # 0.2.0 — Phase 12 Wave 1 W1.4
client.licenses.order(licence, billing_mode, publisher_ids,
quantity=None, terms_accepted=True) # 0.3.0 — licence orders
client.licenses.orders(cursor=None, limit=20) # 0.3.0 — orders + lines
client.licenses.get_order(order_id) # 0.3.0 — + Schedule documents and SHA-256
client.licenses.list() # buyer account + keys
client.rsl.get(publisher_id, jsonld=False) # 0.2.0 — Phase 12 Wave 1 W1.1
client.onboarding.detect_platform(url) # 0.2.0 — Phase 12 Wave 3 W3.1
client.discovery.publisher_directory( # 0.2.0+ — public catalog browse
category=None, min_articles=None, verified=None, limit=None, offset=None,
)
client.discovery.lookup_article(url) # 0.2.0+ — URL → licensable check
client.discovery.verify_license(key) # 0.2.0+ — license key verification
All methods return dicts matching the backend wire format. See docs.opedd.com for full response shapes.
Regulatory framing (CDSM Article 4 vs EU AI Act Article 53)
Three distinct compliance surfaces — never conflated per INVARIANTS.md W1.6:
client.rsl.get(publisher_id, jsonld=True)— publisher-side CDSM Article 4(3) opt-out declaration (signed JSON-LD receipt over the reservation state).client.compliance.article_53_attestation(license_id)— buyer-side EU AI Act Article 53 attestation (signed JWT scoped to one license).client.compliance.report(from_, to)— comprehensive procurement-defense dossier covering BOTH frameworks (publisher CDSM reservation honored + buyer Article 53 evidence chain).
These serve different audit-defensibility modes and never share wire format. Method docstrings cite the W1.6 invariant inline.
Not in SDK
The SDK is buyer-side by design (Stripe-style audience separation — different SDK for publishers if/when that ships). Publisher-side write endpoints are reachable via raw HTTP only, not wrapped here. Canonical excluded surface as of 0.2.0:
PATCH /publisher-profile(Phase 12 Wave 1 W1.2 — publisher updatestdm_reservation_signed_at+cdsm_tdm_opt_outfrom the publisher dashboard). The endpoint exists on the backend but is not yet documented inopedd-docs/openapi.jsonand uses publisher-portal Supabase JWT auth (a credential mode the buyer-side SDK does not carry). The publisher React dashboard atopedd-frontendcalls it via rawfetch().
Reconsider expanding the SDK surface to publisher-side write endpoints when both of these hold: (i) opedd-docs/openapi.json includes the /publisher-profile PATCH spec, and (ii) a buyer integration ask for tdm_reservation state surfaces. Tracked in opedd-backend/docs/cleanup/active-deferrals.md for the institutional record.
Error handling
from opedd import (
OpeddError,
OpeddAuthError,
OpeddNotFoundError,
OpeddRateLimitError,
OpeddServerError,
OpeddValidationError,
)
try:
article = client.content.get(uuid)
except OpeddRateLimitError as e:
time.sleep(e.retry_after_seconds or 60)
retry()
except OpeddAuthError:
refresh_token()
except OpeddNotFoundError:
log.warning("article gone; skipping")
except OpeddError as e:
log.error(f"{e} request_id={e.request_id}")
Every error carries status_code, request_id, and body for forensic correlation.
Schema version pinning
The SDK ships pinned to backend schema version phase-11-m4 (opedd.__schema_version__).
When the backend bumps X-Opedd-Schema-Version, the SDK ships a follow-up release within 1 sprint per the schema-pin invariant.
Additive field bumps are absorbed transparently (dict pass-through). Subtractive or rename bumps require an SDK release; ensure your requirements.txt pins to a tested version.
Tests
Two test suites:
Unit tests (run in CI, no live API calls)
pip install -e ".[dev]"
pytest tests/test_unit.py
29+ unit tests covering: client construction, credential precedence, env-var fallback, auth-header building per credential type, HTTP error mapping (401/403/404/400/422/429/5xx), NDJSON streaming + cursor pagination, all 5 namespaces' request shapes.
Integration tests (manual, before each release)
export OPEDD_BUYER_JWT="..." # for /buyer-audit + /buyer-compliance-report
export OPEDD_BUYER_TOKEN="..." # for /content-delivery
export OPEDD_ACCESS_KEY="..." # for /enterprise-license GET feed
pytest --integration
Live tests against api.opedd.com. Read-only. Skipped by default (require --integration flag).
Per release discipline, integration tests must pass locally before any version tag is pushed. CI does NOT run integration tests — per institutional risk discipline, autonomous CI runs against production state can pollute usage_records, distort metered-publisher payouts, and fire production API calls at scale.
Development
git clone https://github.com/Opedd/opedd-python.git
cd opedd-python
pip install -e ".[dev]"
pytest tests/test_unit.py # unit only (default)
pytest --integration # unit + integration (requires env vars)
ruff check src/ tests/ # lint
mypy src/ # type-check
Contributing
Issues and PRs welcome at github.com/Opedd/opedd-python. For broader questions about Opedd as a platform, email support@opedd.com.
License
See also
- docs.opedd.com — full API reference + cookbook
- opedd-mcp — Model Context Protocol server for Claude Desktop / Cursor
- opedd-backend — backend implementation (private)
Release files for opedd 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| opedd-0.3.1.tar.gz | 39.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| opedd-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.0 kB
Release files / opedd-0.3.1.tar.gz
| Download URL | opedd-0.3.1.tar.gz |
|---|---|
| Size | 39.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d8ad135f42c9122b5b186be059e5debf776bbe6abeec7d40056f6a4cf54916e7
|
|
BLAKE2b-256 checksum How to use checksums |
f2f3b50b683dd99c6ffcc257b116f8e260b62e40e6efe00fd6d7a44a892d83f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / opedd-0.3.1-py3-none-any.whl
| Download URL | opedd-0.3.1-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
16b64eb9e1462b7d0abb150412579ecbdfe7692dbacb56f31a944738547e21cb
|
|
BLAKE2b-256 checksum How to use checksums |
069a82b0bd093e439d8a79f5f9cc7b26f4d814413fd9488974d6382f622000e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|