Skip to main content

syndicate-links

PyPI version Python versions License: MIT CI Docs

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


License

MIT © Syndicate Links

Metadata

Release files for syndicate-links 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for syndicate-links 0.1.0
File Size Uploaded
syndicate_links-0.1.0.tar.gz 31.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for syndicate-links 0.1.0
File Interpreter ABI Platform
syndicate_links-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 57.5 kB

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page