Official Python SDK for Syndicate Links — the agent-native affiliate and commission platform.
Project description
syndicate-links
Official Python SDK for Syndicate Links — the agent-native affiliate and commission platform. Works for SaaS and physical-goods merchants. Native support for AI agent publishers.
Type-checked Pydantic models, sync and async clients, automatic cursor pagination, retries with exponential backoff, and a clean exception hierarchy. One package, one API key, every endpoint.
Installation
pip install syndicate-links
Requires Python 3.9+. Depends only on httpx and pydantic v2.
Quick start
1. Merchant: launch a program and add products
from syndicate_links import SyndicateClient
client = SyndicateClient(api_key="mk_live_…")
program = client.merchant.programs.create(
name="Acme Pro",
default_commission_pct=20,
cookie_days=30,
auto_approve=True,
category="saas",
)
client.merchant.products.create(
program_id=program.id,
name="Acme Pro (Monthly)",
url="https://acme.com/pro",
price=49.00,
commission_pct=25, # overrides program default
)
# Or import an entire catalog in one shot
client.merchant.products.bulk_create(
program_id=program.id,
products=[
{"name": "Starter", "url": "https://acme.com/starter", "price": 9},
{"name": "Team", "url": "https://acme.com/team", "price": 29},
{"name": "Pro", "url": "https://acme.com/pro", "price": 49},
],
)
2. Affiliate / publisher: join a program and create a tracking link
from syndicate_links import SyndicateClient
client = SyndicateClient(api_key="ak_live_…")
# Discover programs
for program in client.affiliate.programs.iter_all(category="saas"):
print(f"{program.name} — {program.default_commission_pct}%")
# Apply (auto-approve programs return status='approved' instantly)
partnership = client.affiliate.programs.apply(program_id="prog_…")
# Mint a tracking link
link = client.affiliate.links.create(
program_id="prog_…",
destination_url="https://acme.com/pro",
source_tag="newsletter-april",
)
print(f"Share this code: {link.code}")
3. AI agent: mint an attribution token and report a conversion
from syndicate_links import SyndicateClient
# Agent keys begin with `aff_agent_` and route to the affiliate sub-client.
agent = SyndicateClient(api_key="aff_agent_…")
# 1. Tracking link for the agent's recommendation
link = agent.affiliate.links.create(
program_id="prog_…",
destination_url="https://acme.com/pro",
source_tag="chatgpt-plugin",
)
# 2. Sign an attribution token the merchant can later verify
token = agent.affiliate.attribution_token.create(
program_id="prog_…",
tracking_code=link.code,
agent_id="acme-research-bot",
surface="chatgpt",
ttl_seconds=3600,
)
# 3. When the user converts, record it with AI-native attribution
conversion = agent.affiliate.events.conversion(
tracking_code=link.code,
order_id="ord_42",
sale_amount=49.00,
ai_referral="ai", # → API attributionMethod
ai_surface="chatgpt", # → flows into AI endorsement reports
)
print(f"Earned ${conversion.commission_amount} on order {conversion.order_id}")
Authentication
The Syndicate Links API uses bearer tokens. The SDK auto-detects the key type from its prefix and routes you to the correct sub-client:
| Prefix | Key type | Sub-client |
|---|---|---|
mk_live_ |
Merchant | client.merchant.* |
ak_live_ |
Affiliate / human | client.affiliate.* |
aff_agent_ |
Affiliate / AI agent | client.affiliate.* |
Pass the key to the constructor:
client = SyndicateClient(
api_key="mk_live_…",
base_url="https://api.syndicatelinks.co", # override for dev
timeout=30.0,
max_retries=3,
)
If you try to access the wrong sub-client you get a clear, immediate error:
client = SyndicateClient(api_key="ak_live_…")
client.merchant.programs.list()
# raises ForbiddenError:
# "Merchant methods require a merchant key (mk_live_...),
# but an affiliate key was provided."
Get an API key by signing up at https://syndicatelinks.co (or call client.merchant.register(...) / client.affiliate.register(...) programmatically — the API key is returned once so save it immediately).
Pagination
Every list endpoint is cursor-paginated. You have two options.
Manual pagination
page = client.merchant.programs.list(limit=50)
print(f"Got {len(page.data)} programs, more = {page.has_more}")
while page.has_more:
page = client.merchant.programs.list(cursor=page.cursor, limit=50)
for program in page.data:
...
Auto-pagination with iter_all()
Every list method has a sibling iter_all() generator that walks the cursor for you:
for program in client.merchant.programs.iter_all():
print(program.name)
for product in client.merchant.products.iter_all(program_id="prog_…"):
print(product.name, product.price)
for partnership in client.merchant.affiliates.iter_all(status="pending"):
client.merchant.affiliates.approve(partnership.partnership.id)
Error handling
All errors inherit from SyndicateError. HTTP errors raise the matching subclass so you can catch by status:
SyndicateError
└── APIError
├── ValidationError (400)
├── AuthenticationError (401)
├── ForbiddenError (403) ← named "ForbiddenError" so it
├── NotFoundError (404) doesn't shadow Python's built-in
├── ConflictError (409) PermissionError
├── RateLimitError (429)
└── ServerError (5xx, after retries)
└── NetworkError (timeout, DNS, connection reset)
from syndicate_links import (
SyndicateClient,
AuthenticationError,
NotFoundError,
ValidationError,
NetworkError,
)
client = SyndicateClient(api_key="mk_live_…")
try:
program = client.merchant.programs.update("prog_does_not_exist", name="x")
except NotFoundError as e:
print(f"Nope: {e.message}")
except ValidationError as e:
print(f"Bad input: {e.message} ({e.response})")
except AuthenticationError:
print("Key is invalid or expired")
except NetworkError as e:
print(f"Couldn't reach the API: {e}")
5xx responses and network errors are automatically retried up to max_retries times (default 3) with exponential backoff and jitter. 4xx errors are raised immediately.
Async usage
For async codebases, swap in AsyncSyndicateClient. Same arguments, same sub-clients, every method is a coroutine.
import asyncio
from syndicate_links import AsyncSyndicateClient
async def main() -> None:
async with AsyncSyndicateClient(api_key="mk_live_…") as client:
program = await client.merchant.programs.create(
name="Acme Pro",
default_commission_pct=20,
)
async for product in client.merchant.products.iter_all(program_id=program.id):
print(product.name)
asyncio.run(main())
Full API reference
Merchant (client.merchant)
| Method | Description |
|---|---|
merchant.register(name, email, …) |
Create a merchant account; returns the API key once. |
merchant.programs.list(cursor, limit) |
List your programs (paginated). |
merchant.programs.iter_all() |
Iterate every program across pages. |
merchant.programs.create(name, default_commission_pct, **kwargs) |
Launch a new program. |
merchant.programs.update(program_id, **fields) |
Partial update. |
merchant.products.list(program_id, cursor, limit) |
List products. |
merchant.products.iter_all(program_id) |
Iterate every product. |
merchant.products.create(program_id, name, url, price, **kwargs) |
Create one product. |
merchant.products.bulk_create(program_id, products) |
Bulk import — returns {"inserted": n}. |
merchant.products.update(product_id, **fields) |
Partial update. |
merchant.products.delete(product_id) |
Delete a product. |
merchant.affiliates.list(status, cursor, limit) |
List partnerships in your programs. |
merchant.affiliates.iter_all(status) |
Iterate every partnership. |
merchant.affiliates.approve(partnership_id) |
Approve a pending partnership. |
merchant.affiliates.reject(partnership_id) |
Reject a pending partnership. |
merchant.conversions.list(cursor, limit) |
List conversions across your programs. |
merchant.conversions.create(tracking_code, order_id, sale_amount, currency) |
Server-side conversion report. |
merchant.refunds.create(order_id, refund_amount, reason) |
Issue a refund and claw back commission. |
merchant.reports.summary(start_date, end_date) |
Aggregate clicks / conversions / revenue. |
merchant.reports.ai_endorsements(start_date, end_date) |
AI-attributed conversion breakdown. |
merchant.billing.get() |
Plan, usage limits, tier info. |
merchant.profile.get() / update(**fields) |
Read / update company profile. |
merchant.payouts.list(cursor, limit) / stats() |
Payouts to your affiliates. |
merchant.settings.get() |
Composite settings (company + webhooks + Stripe). |
merchant.webhooks.create(url, events, secret) |
Register a webhook (secret returned once). |
merchant.webhooks.list() |
List your registered webhooks. |
merchant.webhooks.delete(webhook_id) |
Remove a webhook. |
Affiliate / publisher (client.affiliate)
| Method | Description |
|---|---|
affiliate.register(name, email, type, …) |
Create an affiliate / agent account; API key returned once. |
affiliate.programs.list(cursor, limit, sort, category, search) |
Browse the public program catalog. |
affiliate.programs.iter_all(...) |
Iterate every public program. |
affiliate.programs.mine(cursor, limit) |
List programs you've joined. |
affiliate.programs.get(program_id) |
Fetch a single program. |
affiliate.programs.products(program_id, cursor, limit) |
List a program's products. |
affiliate.programs.apply(program_id) |
Apply for a partnership. |
affiliate.products.search(query, category, cursor, limit) |
Full-text product search across the catalog. |
affiliate.partnerships.list(cursor, limit) |
List your partnership applications. |
affiliate.links.create(program_id, destination_url, product_id, source_tag) |
Mint a tracking link. |
affiliate.links.list(cursor, limit) |
List your tracking links. |
affiliate.events.list(cursor, limit) |
Stream of clicks + conversions you've generated. |
affiliate.events.click(tracking_code, ip_hash, user_agent, referer) |
Programmatically record a click. |
affiliate.events.conversion(tracking_code, order_id, sale_amount, currency, ai_referral, ai_surface) |
AI-native conversion with attribution metadata. |
affiliate.reports.earnings(start_date, end_date) |
Daily earnings time series. |
affiliate.reports.clicks(start_date, end_date) |
Click totals. |
affiliate.reports.conversions(start_date, end_date) |
Conversion totals + revenue. |
affiliate.me.get() / update(**fields) |
Read / update your profile. |
affiliate.me.balance() |
Available, pending, and lifetime commission balance. |
affiliate.me.payouts(cursor, limit) |
Your past payouts. |
affiliate.payouts.claim(amount, rail, invoice, currency) |
Self-serve withdrawal (Lightning today). |
affiliate.dashboard.summary() |
Aggregate stats across all your programs. |
affiliate.dashboard.earnings_chart(days) |
Daily earnings for the last N days. |
affiliate.keys.create(name) |
Mint an additional agent API key. |
affiliate.attribution_token.create(program_id, tracking_code, agent_id, surface, ttl_seconds) |
Sign an agent attribution token. |
Links
- 🌐 Website: https://syndicatelinks.co
- 📚 SDK docs: https://syndicatelinks.co/docs/python-sdk
- 📖 API reference: https://syndicatelinks.co/docs/api
- 🐛 Issues: https://github.com/syndicate-links/syndicate-links-python/issues
- 💬 Discord: https://syndicatelinks.co/discord
License
MIT © Syndicate Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file syndicate_links-0.1.0.tar.gz.
File metadata
- Download URL: syndicate_links-0.1.0.tar.gz
- Upload date:
- Size: 31.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12d1b40eaac6a6216e8a88e15567ec296a466e07316c0546db06f5050baccefa
|
|
| MD5 |
f8378d5a75ec5f6fdf27af67b519935c
|
|
| BLAKE2b-256 |
41a7a714b59304dd28c35ce12ed77465b207ddb3fdfac67c5bfff941b953e022
|
File details
Details for the file syndicate_links-0.1.0-py3-none-any.whl.
File metadata
- Download URL: syndicate_links-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17fa64dbac3f3c10658415d68353811635568d8586e670a8b52fc33852947344
|
|
| MD5 |
70157302e72a68e4e199e5121991f383
|
|
| BLAKE2b-256 |
f701bef5e4bffdbb9d399a74c8399ac962f700b198465539c76d91c0b6519efc
|