Skip to main content

Official Python SDK for Syndicate Links — the agent-native affiliate and commission platform.

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

syndicate_links-0.1.0.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

syndicate_links-0.1.0-py3-none-any.whl (25.7 kB view details)

Uploaded Python 3

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page