# Commissions Source: https://agentref.co/docs/affiliates/commissions How affiliate commissions are calculated and tracked. ## How Commissions Work When someone you referred makes a purchase, you earn a commission. The amount depends on the program's commission structure. ## Commission Types | Type | How It Works | Example | | ------------------------ | ---------------------------------------------------------------- | ---------------------------- | | **One-time** | Percentage commission on the initial purchase only | 30% of a $99 sale = $29.70 | | **Recurring** | Percentage commission on each subscription renewal | 20% of every monthly renewal | | **Recurring with limit** | Recurring percentage commission capped to a set number of months | 20% for the first 12 months | The commission type and rate are set by the merchant for each program. ## Commission Lifecycle Every commission goes through these stages: ``` Conversion Created → Pending → Approved → Paid ``` | Status | Description | | -------------------- | --------------------------------------------------------------------- | | `pending` | Conversion recorded, waiting for approval period | | `approved` | Commission confirmed, waiting for payout threshold | | `paid` | Included in a payout, money sent to you | | `partially_refunded` | Customer received a partial refund, commission reduced proportionally | | `refunded` | Customer fully refunded, commission reversed | ### Why Pending? Commissions start as **pending** to protect against refunds and fraud. The approval period varies by program – typically 14-30 days. This is standard practice in affiliate marketing. ## Viewing Your Commissions ### Dashboard Your affiliate dashboard shows: * Total commissions earned (all time) * Pending commissions (not yet approved) * Available for payout (approved, waiting for threshold) * Commission history with per-conversion detail ### Via API Use `GET /me/earnings` for the cross-program overview and `GET /me/earnings/{programId}` when you want line-item detail for one program. ```javascript Node.js theme={null} const headers = { Authorization: 'Bearer ak_aff_YOUR_KEY', }; const overview = await fetch('https://www.agentref.co/api/v1/me/earnings', { headers }).then((r) => r.json()); const details = await fetch( 'https://www.agentref.co/api/v1/me/earnings/PROGRAM_UUID?period=30d', { headers } ).then((r) => r.json()); ``` ```python Python theme={null} import httpx headers = {"Authorization": "Bearer ak_aff_YOUR_KEY"} overview = httpx.get("https://www.agentref.co/api/v1/me/earnings", headers=headers).json() details = httpx.get( "https://www.agentref.co/api/v1/me/earnings/PROGRAM_UUID", headers=headers, params={"period": "30d"}, ).json() ``` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/me/earnings" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" curl "https://www.agentref.co/api/v1/me/earnings/PROGRAM_UUID?period=30d" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" ``` ## Recurring Commissions Some programs offer **recurring commissions** on subscription products. If a customer you referred stays subscribed, you earn a commission on each renewal – not just the first payment. Whether a program offers one-time or recurring commissions is set by the merchant. Check the program details when you join. ## Refunds If a customer receives a partial refund, the commission is reduced proportionally and the conversion can move to `partially_refunded`. If the customer receives a full refund, the conversion status changes to `refunded` and the commission is reversed. Refunded commissions that were already paid out are deducted from your next payout. # Affiliate FAQ Source: https://agentref.co/docs/affiliates/faq Frequently asked questions about being an affiliate on AgentRef. ## Getting Started Either click an invite link from a merchant, or browse the [AgentRef Marketplace](https://www.agentref.co/marketplace) and apply to programs that interest you. No. Joining as an affiliate is completely free. AgentRef charges the merchant, not the affiliate. Yes. You can be an affiliate for as many programs as you want. Each program has its own referral code, commission rate, and payout settings. No. You just need to share your referral link. If you're a developer or AI agent, you can also use the API to automate your workflow. ## Tracking & Attribution When someone clicks your referral link, a first-party cookie is set on their browser. If they make a purchase within the cookie window (set by the merchant, typically 30-90 days), the conversion is attributed to you. Cookie-based tracking is per-browser. If a visitor clicks your link on mobile but purchases on desktop, the conversion won't be automatically attributed. This is a limitation of cookie-based tracking across the industry. Yes. Use sub-IDs (`sub1`-`sub5`) in your links to track different placements, and view the breakdown in your dashboard. ## Commissions & Payouts Once your approved commissions reach the payout threshold (default: \$50), the merchant can create a payout and send the money to your saved payout method. Timing depends on the merchant's payout cadence and the external payment provider. New commissions start as pending to allow for refunds and fraud review. The approval period is set by the merchant (typically 14-30 days). Once approved, commissions count toward your payout threshold. The commission is reversed. If it was already paid out, the amount is deducted from your next payout. AgentRef supports all currencies that Stripe supports. Your commission is earned in the same currency as the merchant's product. Stripe handles currency conversion when paying out to your bank. You do not connect a Stripe account for affiliate payouts today. Go to **Settings → Payouts** and save your payout details instead, using either PayPal or bank transfer. ## Account & API Yes. Create an affiliate API key (`ak_aff_*`) in your dashboard under **Settings → API Keys**. Use the [REST API](/docs/api-reference/authentication) for `/me/*` endpoints or the [MCP Server](/docs/agent-integration/mcp-server) for affiliate tools like link generation and earnings lookups. Yes – AgentRef is agent-compatible. AI agents can join programs, manage links, review click stats, and monitor earnings through the REST API or [MCP Server](/docs/agent-integration/mcp-server). Contact the merchant who runs the program. Referral code changes are managed by the merchant to prevent tracking disruptions. # Getting Paid Source: https://agentref.co/docs/affiliates/getting-paid How and when affiliate payouts are processed. ## Payout Process Once your approved commissions reach the payout threshold, the merchant can create a payout in AgentRef, send the money externally, and mark the payout status as it progresses. Today this is a manual merchant workflow. AgentRef tracks payout state and payout details, but does not automatically send affiliate funds. ### How It Works As your referrals convert, commissions move from `pending` → `approved`. When your approved balance reaches the program's payout threshold (default: \$50), you become payout-eligible. The merchant creates a payout record, sends the money outside AgentRef (for example via PayPal or bank transfer), and records the status in AgentRef. Once the merchant marks the payout as completed, it appears in your payout history. Arrival time depends on the external payment provider. ## Setting Your Payout Details To receive payouts smoothly, add your payout details in AgentRef: 1. Go to **Settings → Payouts** in your affiliate dashboard 2. Choose your payout method: `paypal` or `bank_transfer` 3. Enter the required payout details 4. Save the profile so merchants can pay you correctly Merchants may hold payouts until your payout details are complete. Add them before your first payout becomes due. ## Payout Schedule | Setting | Default | | ---------------- | ----------------------- | | Payout threshold | \$50 USD | | Processing time | Merchant-defined | | Payment method | PayPal or bank transfer | The merchant can configure the payout threshold per program. ## Viewing Payout History ### Dashboard Your dashboard shows all payouts with status, amount, and date. ### Via API Use `GET /me/payout-info` to check the payout method on file and `GET /me/payouts` to review payout history. ```javascript Node.js theme={null} const headers = { Authorization: 'Bearer ak_aff_YOUR_KEY', }; const payoutInfo = await fetch('https://www.agentref.co/api/v1/me/payout-info', { headers }).then((r) => r.json()); const payouts = await fetch('https://www.agentref.co/api/v1/me/payouts', { headers }).then((r) => r.json()); ``` ```python Python theme={null} import httpx headers = {"Authorization": "Bearer ak_aff_YOUR_KEY"} payout_info = httpx.get("https://www.agentref.co/api/v1/me/payout-info", headers=headers).json() payouts = httpx.get("https://www.agentref.co/api/v1/me/payouts", headers=headers).json() ``` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/me/payout-info" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" curl "https://www.agentref.co/api/v1/me/payouts" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" ``` ## Payout Statuses | Status | Meaning | | ------------ | -------------------------------------------------- | | `pending` | Payout created, waiting to be processed | | `processing` | Merchant started the external payment step | | `completed` | Merchant confirmed the payout as paid | | `failed` | External payment attempt failed and can be retried | ## Common Issues The most common reasons are incomplete payout details or a failed external payment attempt. Go to **Settings → Payouts** and confirm your PayPal or bank transfer details are correct. Check if your approved balance has reached the payout threshold. Also confirm your payout profile is complete and ask the merchant which payout cadence they use. If a customer requested a refund, the associated commission is reversed. Refunds on already-paid commissions are deducted from your next payout. # Getting Started as an Affiliate Source: https://agentref.co/docs/affiliates/getting-started How to join an affiliate program on AgentRef and start earning commissions. This guide is for **affiliates** – people who promote products and earn commissions on successful referrals. ## How to Join a Program There are two ways to join an affiliate program on AgentRef: ### 1. Invite Link If a merchant sent you an invite link, click it to create your affiliate account. You'll be asked to: 1. Enter your name and email 2. Set your preferred referral code (e.g., `jane`, `techreviewer`) 3. Accept the program terms Depending on the program settings, you'll either be approved immediately or placed in a pending queue for the merchant to review. ### 2. Marketplace Browse the [AgentRef Marketplace](https://www.agentref.co/marketplace) to discover programs: 1. Browse available programs 2. Click **Apply** on a program that interests you 3. Fill out the application form 4. Wait for the merchant to approve your application ## Your Affiliate Dashboard Once approved, you'll have access to your affiliate dashboard where you can: * **View your referral links** – unique URLs to share with your audience * **Track clicks** – see how many people clicked your links * **Monitor conversions** – see which clicks converted into sales * **View commissions** – track your earnings in real-time * **Use marketing resources** – download merchant-approved assets and render social posts with your link * **Check payout status** – see when you'll get paid ## Your API Key If you're an AI agent or developer, you can access your affiliate data programmatically: 1. Go to **Settings → API Keys** in your affiliate dashboard 2. Create a new API key (prefix: `ak_aff_`) 3. Use it with the REST API or [MCP Server](/docs/agent-integration/mcp-server) The most useful starting endpoints are: * `GET /me` for your affiliate identity * `GET /me/overview` for aggregate earnings and program stats * `GET /me/programs` for the programs you belong to * `GET /me/clicks` for click stats over a date range * `GET /me/programs/{programId}/marketing-resources` for merchant-published campaign material ```javascript Node.js theme={null} const headers = { Authorization: 'Bearer ak_aff_YOUR_KEY', }; const me = await fetch('https://www.agentref.co/api/v1/me', { headers }).then((r) => r.json()); const overview = await fetch('https://www.agentref.co/api/v1/me/overview', { headers }).then((r) => r.json()); const programs = await fetch('https://www.agentref.co/api/v1/me/programs', { headers }).then((r) => r.json()); const clicks = await fetch( 'https://www.agentref.co/api/v1/me/clicks?startDate=START_DATE&endDate=END_DATE', { headers } ).then((r) => r.json()); ``` ```python Python theme={null} import httpx headers = {"Authorization": "Bearer ak_aff_YOUR_KEY"} me = httpx.get("https://www.agentref.co/api/v1/me", headers=headers).json() overview = httpx.get("https://www.agentref.co/api/v1/me/overview", headers=headers).json() programs = httpx.get("https://www.agentref.co/api/v1/me/programs", headers=headers).json() clicks = httpx.get( "https://www.agentref.co/api/v1/me/clicks", headers=headers, params={"startDate": "START_DATE", "endDate": "END_DATE"}, ).json() ``` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/me" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" curl "https://www.agentref.co/api/v1/me/overview" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" curl "https://www.agentref.co/api/v1/me/programs" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" curl "https://www.agentref.co/api/v1/me/clicks?startDate=START_DATE&endDate=END_DATE" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" ``` ## What's Next Learn how tracking links work Use merchant-provided campaign assets Understand how you earn How and when you get paid Common questions answered # Links & Attribution Source: https://agentref.co/docs/affiliates/links-attribution How affiliate tracking links work and how clicks are attributed to you. ## Your Referral Link Every affiliate gets a unique referral code. Your tracking link follows this format: ``` https://merchant-site.com/?via=YOUR_CODE ``` AgentRef-generated links default to `?via=YOUR_CODE`. Some programs may still expose `ref`, `r`, `a`, or merchant-configured aliases for inbound attribution. When someone clicks your link, AgentRef: 1. Records the click with your affiliate ID 2. Sets a first-party cookie on the visitor's browser 3. Tracks the visitor across the cookie window (typically 30-90 days) 4. Attributes any conversion back to you ## Referral Code Your referral code is set when you join a program. It's usually your name or brand: ``` https://example.com/?via=jane https://example.com/?via=techreviewer https://example.com/?via=ai-tools-blog ``` ## Creating links via API Affiliate API keys can create named tracking links for a joined program. Use `destination_path` for a merchant-approved path on the merchant website and `custom_slug` when you want a specific referral code for that link. `destination_path` must be a relative path such as `/pricing` or `/blog/review`. It must be in the program's allowed destination list. `custom_slug` must be 3-32 lowercase letters, numbers, or hyphens. ```bash cURL theme={null} curl -X POST "https://www.agentref.co/api/v1/me/links?program_id=PROGRAM_ID" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: create-pricing-link" \ -d '{ "name": "Pricing page", "destination_path": "/pricing", "custom_slug": "jane-review" }' ``` ```text MCP theme={null} create_link({ program_id: "PROGRAM_ID", name: "Pricing page", destination_path: "/pricing", custom_slug: "jane-review", idempotency_key: "create-pricing-link" }) ``` ## Updating links via API The current update contract is narrower than the create contract. `PATCH /api/v1/me/links/{id}` accepts `name`, `targetUrl`, and `isActive`; it does not accept `destination_path` or `custom_slug`. Use `targetUrl` when you need to point an existing link at a full URL, and create a new link when you need a different approved destination path or custom slug. ```bash cURL theme={null} curl -X PATCH "https://www.agentref.co/api/v1/me/links/LINK_ID?program_id=PROGRAM_ID" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Pricing page - updated", "targetUrl": "https://merchant-site.com/pricing", "isActive": true }' ``` ```javascript Node.js theme={null} await client.affiliateWorkspace.updateLink( 'LINK_ID', { name: 'Pricing page - updated', targetUrl: 'https://merchant-site.com/pricing', isActive: true, }, { programId: 'PROGRAM_ID' } ); ``` ```python Python theme={null} client.affiliate_workspace.update_link( "LINK_ID", name="Pricing page - updated", target_url="https://merchant-site.com/pricing", is_active=True, program_id="PROGRAM_ID", ) ``` ## Cookie Duration Each program sets a **cookie window** – the number of days a click stays valid. If a visitor clicks your link today and purchases within the window, you get credit. | Typical Windows | Duration | | --------------- | -------- | | Short | 30 days | | Standard | 60 days | | Long | 90 days | The merchant configures this per program. ## UTM & Sub-ID Tracking You can append tracking parameters to your links for your own analytics: ``` https://example.com/?via=jane&utm_source=youtube&utm_campaign=review https://example.com/?via=jane&sub1=header-banner&sub2=homepage ``` ### Supported Parameters | Parameter | Description | | --------------- | --------------------------------------------- | | `utm_source` | Traffic source (youtube, twitter, newsletter) | | `utm_medium` | Medium (video, post, email) | | `utm_campaign` | Campaign name | | `utm_content` | Content variant | | `utm_term` | Search term | | `sub1` - `sub5` | Custom sub-IDs for your own tracking | These are tracked per click and visible in your affiliate dashboard. ## Attribution Model AgentRef uses **last-click attribution**: the last affiliate link clicked before conversion gets the commission. If a visitor clicks affiliate A's link, then later clicks affiliate B's link and purchases – affiliate B gets credit. ## Viewing Your Clicks ### In the Dashboard Your dashboard shows click analytics: total clicks, unique visitors, click-through rates, and geographic breakdown. ### Via API Use the affiliate self-serve clicks endpoint to inspect a specific reporting window: ```javascript Node.js theme={null} const stats = await fetch( 'https://www.agentref.co/api/v1/me/clicks?startDate=START_DATE&endDate=END_DATE', { headers: { Authorization: 'Bearer ak_aff_YOUR_KEY' }, } ).then((r) => r.json()); ``` ```python Python theme={null} import httpx stats = httpx.get( "https://www.agentref.co/api/v1/me/clicks", headers={"Authorization": "Bearer ak_aff_YOUR_KEY"}, params={"startDate": "START_DATE", "endDate": "END_DATE"}, ).json() ``` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/me/clicks?startDate=START_DATE&endDate=END_DATE" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" ``` Use sub-IDs (`sub1`-`sub5`) to track which content, placement, or campaign drives the most conversions. This helps you optimize your promotion strategy. # Marketing Resources Source: https://agentref.co/docs/affiliates/marketing-resources Use merchant-provided files, social posts, swipe copy, and links in your affiliate promotions. Marketing Resources are affiliate-ready assets published by a merchant. They can include files, social post templates, swipe copy, external links, and campaign collections. You only see resources for programs where you are an approved affiliate. ## What you can access | Resource | How to use it | | -------------- | ----------------------------------------------------------------------- | | Files | Download images, PDFs, videos, or other campaign assets. | | Social posts | Render post copy with your own affiliate link and referral code. | | Swipe copy | Copy approved messaging for emails, posts, or landing pages. | | External links | Open merchant-hosted folders, brand kits, or campaign pages. | | Collections | Download or browse a grouped set of resources for a launch or campaign. | ## Surface coverage Affiliate REST, SDK, and MCP access is read-oriented. Affiliates can list published resources, inspect a resource, render social post templates, and create download URLs. Creating or editing merchant resources is a merchant-only workflow. | Capability | Dashboard | REST API | Node/Python SDKs | Affiliate MCP | | --------------------------------------------- | --------- | -------- | ---------------- | ------------- | | List published resources for a joined program | Yes | Yes | Yes | Yes | | Get resource detail | Yes | Yes | Yes | Yes | | Render social post placeholders | Yes | Yes | Yes | Yes | | Create download URLs | Yes | Yes | Yes | Yes | | Create or edit resources | No | No | No | No | Marketing Resources do not currently emit webhook events. Merchants publish or unpublish resources, and affiliates read the current published state. ## Rendered social posts Merchants can publish social post templates with placeholders: | Placeholder | Rendered value | | -------------------- | ----------------------------------- | | `{{affiliate_link}}` | Your tracking link for the program. | | `{{affiliate_code}}` | Your referral code. | | `{{program_name}}` | The program name. | When you render a social post, AgentRef returns the final copy, available links, attached media, merchant instructions, and disclosure guidance. If you share a post with your affiliate link, include a clear disclosure where people can see it. Examples include `#ad`, `affiliate link`, or equivalent wording that fits the platform and local rules. ## REST API Affiliate Marketing Resources require an affiliate API key (`ak_aff_*`) with `marketing_resources:read`. | Action | Endpoint | | ---------------------------- | ----------------------------------------------------------------------- | | List resources for a program | `GET /api/v1/me/programs/{programId}/marketing-resources` | | Get resource detail | `GET /api/v1/me/marketing-resources/{resourceId}?program_id=PROGRAM_ID` | | Render social post | `POST /api/v1/me/marketing-resources/social-posts/{resourceId}/render` | | Create download URL | `POST /api/v1/me/marketing-resources/download-url` | ```bash List resources theme={null} curl "https://www.agentref.co/api/v1/me/programs/PROGRAM_ID/marketing-resources?kind=social_posts" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" ``` ```bash Render a social post theme={null} curl -X POST "https://www.agentref.co/api/v1/me/marketing-resources/social-posts/RESOURCE_ID/render" \ -H "Authorization: Bearer ak_aff_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "program_id": "PROGRAM_ID", "affiliate_link_id": "LINK_ID" }' ``` ## MCP tools Affiliate MCP exposes Marketing Resources through: `list_marketing_resources`, `get_marketing_resource`, `render_marketing_social_post`, and `create_marketing_resource_download_url`. Use the affiliate MCP endpoint: ```text theme={null} https://www.agentref.co/api/mcp/affiliate ``` ## What's next Create and use tracking links. Understand how earnings are calculated. # Agent Patterns Source: https://agentref.co/docs/agent-integration/agent-patterns End-to-end workflow examples for AI agents using AgentRef. Complete code for setting up programs, monitoring conversions, handling fraud, and generating payout reports. This page provides ready-to-use patterns for common agent workflows. Each example shows the complete tool sequence or SDK code an AI agent would use, with both Node.js and Python implementations. ## Pattern 1: Set Up a Complete Affiliate Program This workflow creates a program, connects Stripe, checks readiness, publishes marketplace settings, and invites the first affiliates -- everything a merchant needs to go live from the REST/SDK surface. ```typescript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: process.env.AGENTREF_API_KEY }); // Step 1: Create the program const program = await client.programs.create({ name: 'Acme Pro Referrals', commissionType: 'recurring', commissionPercent: 25, cookieDuration: 60, payoutThreshold: 5000, // $50.00 in cents autoApproveAffiliates: true, currency: 'USD', }, { idempotencyKey: 'setup-acme-pro-v1' }); console.log(`Program created: ${program.id}`); // Step 2: Connect Stripe const stripe = await client.programs.connectStripe(program.id); console.log(`Complete Stripe OAuth at: ${stripe.authUrl}`); // The merchant opens this URL in a browser to connect their Stripe account // Step 3: Check readiness const detail = await client.programs.get(program.id); console.log(`Readiness: ${detail.readiness}`); // Step 4: Publish to marketplace await client.programs.updateMarketplace(program.id, { status: 'public', category: 'SaaS', description: 'Earn 25% recurring commission on every referral.', }); // Step 5: Invite affiliates const invite = await client.programs.createInvite(program.id, { email: 'top-affiliate@example.com', name: 'Top Affiliate', expiresInDays: 14, }, { idempotencyKey: 'invite-top-affiliate-v1' }); console.log(`Invite sent: ${invite.token}`); // Step 6: Create a public invite link for broader distribution const publicInvite = await client.programs.createInvite(program.id, { isPublic: true, usageLimit: 100, expiresInDays: 30, }, { idempotencyKey: 'public-invite-v1' }); ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef() # reads AGENTREF_API_KEY from env # Step 1: Create the program program = client.programs.create( name="Acme Pro Referrals", commission_type="recurring", commission_percent=25, cookie_duration=60, payout_threshold=5000, # $50.00 in cents auto_approve_affiliates=True, currency="USD", idempotency_key="setup-acme-pro-v1", ) print(f"Program created: {program.id}") # Step 2: Connect Stripe stripe = client.programs.connect_stripe(program.id) print(f"Complete Stripe OAuth at: {stripe.auth_url}") # Step 3: Check readiness detail = client.programs.get(program.id) print(f"Readiness: {detail.readiness}") # Step 4: Publish to marketplace client.programs.update_marketplace( program.id, status="public", category="SaaS", description="Earn 25% recurring commission on every referral.", ) # Step 5: Invite affiliates invite = client.programs.create_invite( program.id, email="top-affiliate@example.com", name="Top Affiliate", expires_in_days=14, idempotency_key="invite-top-affiliate-v1", ) print(f"Invite sent: {invite.token}") # Step 6: Public invite link public_invite = client.programs.create_invite( program.id, is_public=True, usage_limit=100, expires_in_days=30, idempotency_key="public-invite-v1", ) ``` *** ## Pattern 2: Monitor Conversions and Flag Fraud This workflow checks recent conversions, reviews fraud flags, and takes action on suspicious activity. Ideal for a scheduled agent task. ```typescript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: process.env.AGENTREF_API_KEY }); // Step 1: Check conversion stats for the last 7 days const stats = await client.conversions.stats({ period: '7d' }); console.log(`Last 7 days: ${stats.total} conversions, $${stats.totalRevenue / 100} revenue`); console.log(`Pending: ${stats.pending}, Approved: ${stats.approved}`); // Step 2: List recent conversions that need review const { data: conversions } = await client.conversions.list({ status: 'pending', limit: 50, }); console.log(`${conversions.length} pending conversions to review`); // Step 3: Check for open fraud flags const flagStats = await client.flags.stats(); console.log(`Open fraud flags: ${flagStats.open}`); if (flagStats.open > 0) { const { data: flags } = await client.flags.list({ status: 'open', limit: 20, }); for (const flag of flags) { console.log(`Flag ${flag.id}: ${flag.type} for affiliate ${flag.affiliateId}`); // Step 4: Auto-dismiss low-risk flags, escalate high-risk if (flag.type === 'high_click_frequency') { // Get the affiliate's full stats for context const affiliate = await client.affiliates.get(flag.affiliateId, { include: 'stats', }); if (affiliate.totalConversions > 0) { // Has real conversions -- likely legitimate traffic await client.flags.resolve(flag.id, { status: 'dismissed', note: `Auto-dismissed: ${affiliate.totalConversions} conversions confirm legitimate traffic`, blockAffiliate: false, }, { idempotencyKey: `resolve-${flag.id}` }); } else { // No conversions -- suspicious, confirm the flag await client.flags.resolve(flag.id, { status: 'confirmed', note: 'Zero conversions with high click volume -- confirmed suspicious', blockAffiliate: true, }, { idempotencyKey: `resolve-${flag.id}` }); } } } } ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef() # Step 1: Check conversion stats for the last 7 days stats = client.conversions.stats(period="7d") print(f"Last 7 days: {stats.total} conversions, ${stats.total_revenue / 100} revenue") print(f"Pending: {stats.pending}, Approved: {stats.approved}") # Step 2: List recent conversions that need review result = client.conversions.list(status="pending", limit=50) print(f"{len(result.data)} pending conversions to review") # Step 3: Check for open fraud flags flag_stats = client.flags.stats() print(f"Open fraud flags: {flag_stats.open}") if flag_stats.open > 0: flags_result = client.flags.list(status="open", limit=20) for flag in flags_result.data: print(f"Flag {flag.id}: {flag.type} for affiliate {flag.affiliate_id}") # Step 4: Auto-dismiss low-risk flags, escalate high-risk if flag.type == "high_click_frequency": affiliate = client.affiliates.get(flag.affiliate_id, include="stats") if affiliate.total_conversions > 0: client.flags.resolve( flag.id, status="dismissed", note=f"Auto-dismissed: {affiliate.total_conversions} conversions confirm legitimate traffic", block_affiliate=False, idempotency_key=f"resolve-{flag.id}", ) else: client.flags.resolve( flag.id, status="confirmed", note="Zero conversions with high click volume -- confirmed suspicious", block_affiliate=True, idempotency_key=f"resolve-{flag.id}", ) ``` *** ## Pattern 3: Generate Weekly Payout Report This workflow lists pending payouts, aggregates stats, and generates a report. Useful for weekly cron jobs or scheduled agent tasks. ```typescript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: process.env.AGENTREF_API_KEY }); // Step 1: Get payout stats const payoutStats = await client.payouts.stats({ period: '30d' }); console.log(`Total paid (30d): $${payoutStats.totalPaid / 100}`); console.log(`Total pending: $${payoutStats.totalPending / 100}`); // Step 2: List all pending affiliates across programs const pending = await client.payouts.listPending(); const report = { generatedAt: new Date().toISOString(), totalPending: pending.meta.total, affiliates: pending.data.map(aff => ({ name: aff.name ?? aff.email, program: aff.programName, amount: `$${aff.pendingAmount / 100}`, currency: aff.currency, method: aff.payoutMethod ?? 'Not set', meetsThreshold: aff.meetsThreshold, commissionCount: aff.commissionCount, })), }; console.log('--- Weekly Payout Report ---'); console.log(JSON.stringify(report, null, 2)); // Step 3: For affiliates who meet the threshold and have a payout method, // create the payout const eligible = pending.data.filter( aff => aff.meetsThreshold && aff.hasPayoutMethod ); console.log(`\n${eligible.length} affiliates eligible for payout`); for (const aff of eligible) { const payout = await client.payouts.create({ affiliateId: aff.affiliateId, programId: aff.programId, method: aff.payoutMethod!, notes: `Weekly payout - ${new Date().toISOString().split('T')[0]}`, }, { idempotencyKey: `weekly-payout-${aff.affiliateId}-${Date.now()}` }); console.log(`Created payout for ${aff.name}: $${aff.pendingAmount / 100}`); } // Step 4: Get recent completed payouts for the report footer const { data: recentPayouts } = await client.payouts.list({ status: 'completed', limit: 10, }); console.log(`\nRecent completed payouts: ${recentPayouts.length}`); ``` ```python Python theme={null} from datetime import datetime from agentref import AgentRef client = AgentRef() # Step 1: Get payout stats payout_stats = client.payouts.stats(period="30d") print(f"Total paid (30d): ${payout_stats.total_paid / 100}") print(f"Total pending: ${payout_stats.total_pending / 100}") # Step 2: List all pending affiliates across programs pending = client.payouts.list_pending() report = { "generated_at": datetime.now().isoformat(), "total_pending": pending.meta.total, "affiliates": [ { "name": aff.name or aff.email, "program": aff.program_name, "amount": f"${aff.pending_amount / 100}", "currency": aff.currency, "method": aff.payout_method or "Not set", "meets_threshold": aff.meets_threshold, "commission_count": aff.commission_count, } for aff in pending.data ], } print("--- Weekly Payout Report ---") import json print(json.dumps(report, indent=2)) # Step 3: Create payouts for eligible affiliates eligible = [ aff for aff in pending.data if aff.meets_threshold and aff.has_payout_method ] print(f"\n{len(eligible)} affiliates eligible for payout") today = datetime.now().strftime("%Y-%m-%d") for aff in eligible: payout = client.payouts.create( affiliate_id=aff.affiliate_id, program_id=aff.program_id, method=aff.payout_method, notes=f"Weekly payout - {today}", idempotency_key=f"weekly-payout-{aff.affiliate_id}-{today}", ) print(f"Created payout for {aff.name}: ${aff.pending_amount / 100}") # Step 4: Recent completed payouts recent = client.payouts.list(status="completed", limit=10) print(f"\nRecent completed payouts: {len(recent.data)}") ``` *** ## Idempotency Patterns for Safe Agent Retries AI agents may retry operations due to timeouts, network failures, or tool-calling loops. Idempotency keys ensure these retries are safe. ### Deterministic Keys Generate idempotency keys from the operation's intent, not random values: ```typescript Node.js theme={null} // Good: deterministic key based on intent const key = `create-program-${programName}-${Date.now()}`; // Better: based on a stable external identifier const key = `invite-${email}-to-${programId}`; // Best: includes a version for intentional re-runs const key = `setup-acme-program-v2`; ``` ```python Python theme={null} # Good: deterministic key based on intent key = f"create-program-{program_name}-{int(time.time())}" # Better: based on a stable external identifier key = f"invite-{email}-to-{program_id}" # Best: includes a version for intentional re-runs key = "setup-acme-program-v2" ``` ### Retry-Safe Workflows When chaining multiple operations, use a unique key for each step: ```typescript Node.js theme={null} const workflowId = 'onboard-acme-2026-03'; // Each step gets its own scoped key const program = await client.programs.create({ name: 'Acme Referrals', commissionType: 'recurring', commissionPercent: 25, }, { idempotencyKey: `${workflowId}-create-program` }); await client.programs.createInvite(program.id, { email: 'partner@example.com', }, { idempotencyKey: `${workflowId}-invite-partner` }); // Review pending applications through the Applications API or MCP review_application tool. // Use a separate key such as `${workflowId}-approve-application-${applicationId}`. ``` ```python Python theme={null} workflow_id = "onboard-acme-2026-03" # Each step gets its own scoped key program = client.programs.create( name="Acme Referrals", commission_type="recurring", commission_percent=25, idempotency_key=f"{workflow_id}-create-program", ) client.programs.create_invite( program.id, email="partner@example.com", idempotency_key=f"{workflow_id}-invite-partner", ) # Review pending applications through the Applications API or MCP review_application tool. # Use a separate key such as f"{workflow_id}-approve-application-{application_id}". ``` *** ## Error Handling Patterns ### Graceful Degradation When an agent encounters an error, it should attempt recovery before giving up: ```typescript Node.js theme={null} import { AgentRef, NotFoundError, RateLimitError, AgentRefError } from 'agentref'; const client = new AgentRef(); async function safeGetProgram(id: string) { try { return await client.programs.get(id); } catch (error) { if (error instanceof NotFoundError) { // Program doesn't exist -- list all programs and pick the first const { data } = await client.programs.list({ status: 'active', limit: 1 }); return data[0] ?? null; } if (error instanceof RateLimitError) { // Wait and retry await new Promise(r => setTimeout(r, error.retryAfter * 1000)); return await client.programs.get(id); } throw error; } } async function safeResolveFlag(flagId: string) { try { await client.flags.resolve(flagId, { status: 'reviewed', note: 'Reviewed by automated agent', }, { idempotencyKey: `auto-review-${flagId}` }); return { success: true }; } catch (error) { if (error instanceof AgentRefError) { return { success: false, error: error.code, message: error.message, requestId: error.requestId, }; } throw error; } } ``` ```python Python theme={null} from agentref import AgentRef, NotFoundError, RateLimitError, AgentRefError import time client = AgentRef() def safe_get_program(program_id: str): try: return client.programs.get(program_id) except NotFoundError: # Program doesn't exist -- list all and pick the first result = client.programs.list(status="active", limit=1) return result.data[0] if result.data else None except RateLimitError as e: # Wait and retry time.sleep(e.retry_after) return client.programs.get(program_id) def safe_resolve_flag(flag_id: str): try: client.flags.resolve( flag_id, status="reviewed", note="Reviewed by automated agent", idempotency_key=f"auto-review-{flag_id}", ) return {"success": True} except AgentRefError as e: return { "success": False, "error": e.code, "message": str(e), "request_id": e.request_id, } ``` ### Logging and Observability Always capture the `requestId` from errors for debugging: ```typescript theme={null} try { await client.conversions.list({ status: 'invalid_status' }); } catch (error) { if (error instanceof AgentRefError) { console.error(`AgentRef error [${error.requestId}]: ${error.code} - ${error.message}`); // Send to your observability platform sentry.captureException(error, { extra: { requestId: error.requestId, code: error.code }, }); } } ``` # CLI Source: https://agentref.co/docs/agent-integration/cli The AgentRef CLI is a thin, automation-friendly command-line interface for managing affiliate programs, listing marketplace offerings, and querying account information. The `agentref` CLI is a lightweight command-line tool that wraps the AgentRef REST API v1 and MCP server. It is designed for scripting, CI/CD pipelines, and quick account operations from the terminal. The CLI reuses existing AgentRef surfaces -- REST API v1 for account/program operations and MCP for marketplace access. It does not duplicate business logic; all domain logic stays on the server. ## Running the CLI From the AgentRef repository: ```bash theme={null} ./bin/agentref help ``` Or via npm: ```bash theme={null} npm run agentref -- help ``` ## Authentication The CLI resolves credentials in this order: 1. `--api-key` flag (highest priority) 2. `AGENTREF_API_KEY` environment variable 3. `~/.agentref/config.json` file ### Config File Store persistent configuration at `~/.agentref/config.json`: ```json title="~/.agentref/config.json" theme={null} { "baseUrl": "https://www.agentref.co", "apiKey": "ak_live_your_api_key_here", "updatedAt": "2026-03-23T10:00:00Z" } ``` If no `--base-url`, `AGENTREF_API_BASE_URL`, saved config, or `NEXT_PUBLIC_APP_URL` is present, the local development CLI falls back to `http://localhost:3000`. Create API keys in the AgentRef dashboard under **Settings > API Keys**. Keys are shown only once -- store them securely. ## Global Options Every command accepts these flags: | Flag | Description | | ------------------- | --------------------------------------- | | `--json` | Stable machine-readable output envelope | | `--base-url ` | Override API base URL | | `--api-key ` | Override stored API key | | `--timeout-ms ` | Request timeout in milliseconds | | `--retries ` | Retry count for retryable failures | ### JSON Output When `--json` is passed, all commands return a consistent envelope: ```json theme={null} { "ok": true, "command": "programs list", "transport": "rest", "data": { "programs": [] }, "meta": { "page": 1, "limit": 20 } } ``` This is useful for piping into `jq` or other automation tools. ## Commands ### `whoami` Display the authenticated user identity. ```bash theme={null} agentref whoami ``` ### `merchant me` Show full merchant account details. ```bash theme={null} agentref merchant me agentref me # alias ``` ### `merchant programs list` List your affiliate programs. ```bash theme={null} agentref merchant programs list agentref merchant programs list --limit 5 --page 2 agentref merchant programs list --json agentref programs list # alias ``` | Flag | Description | | ----------------- | --------------------------- | | `--limit ` | Number of programs per page | | `--page ` | Page number | | `--page-size ` | Items per page | | `--offset ` | Offset-based pagination | ### `merchant programs create` Create a new affiliate program. ```bash theme={null} agentref merchant programs create \ --name "Acme Referrals" \ --commission-type recurring \ --commission-percent 25 agentref programs create \ --name "Acme Referrals" \ --commission-type recurring \ --commission-percent 25 ``` | Flag | Required | Description | | ------------------------------ | -------- | ----------------------------------------------- | | `--name ` | Yes | Program name | | `--commission-type ` | Yes | `one_time`, `recurring`, or `recurring_limited` | | `--commission-percent <1-100>` | Yes | Commission percentage | Additional optional flags match the `create_program` API parameters (cookie duration, payout threshold, etc.). ### `affiliate me` Show the current affiliate workspace identity. ```bash theme={null} agentref affiliate me ``` ### `affiliate overview` Show aggregate affiliate performance across joined programs. ```bash theme={null} agentref affiliate overview ``` ### `affiliate programs list` List programs joined by the current affiliate account. ```bash theme={null} agentref affiliate programs list ``` ### `affiliate programs get` Load one joined program context. ```bash theme={null} agentref affiliate programs get --program-id PROGRAM_ID ``` ### `affiliate marketplace list` Browse the public marketplace catalog. ```bash theme={null} agentref affiliate marketplace list agentref affiliate marketplace list --via mcp agentref marketplace list # alias ``` | Flag | Description | | --------------------- | ---------------------------------------- | | `--via ` | Transport layer to use (default: `rest`) | | Standard filter flags | Category, min commission, etc. | The `--via mcp` option routes the request through the MCP endpoint instead of REST, which can be useful for testing MCP connectivity. ## Exit Codes The CLI uses structured exit codes for scripting: | Code | Meaning | | ---- | --------------------------------------------- | | `0` | Success | | `1` | Unknown / unexpected error | | `2` | Usage error (invalid arguments) | | `3` | Config error (missing config file or API key) | | `4` | Auth error (401 / 403) | | `5` | Rate limited (429) | | `6` | Network / timeout error | | `7` | Server error (5xx) | | `8` | API / MCP contract or client error | ### Scripting with Exit Codes ```bash theme={null} agentref whoami --json if [ $? -eq 4 ]; then echo "API key is invalid or expired" exit 1 fi ``` ## Examples ### Quick status check ```bash theme={null} # Verify credentials work agentref whoami # List programs as JSON for parsing agentref programs list --json | jq '.data[] | {name, status}' ``` ### Create a program from a script ```bash theme={null} #!/bin/bash set -e export AGENTREF_API_KEY="ak_live_..." agentref programs create \ --name "Q1 Referral Program" \ --commission-type one_time \ --commission-percent 15 \ --json echo "Program created successfully" ``` ### CI/CD integration ```yaml title=".github/workflows/setup-program.yml" theme={null} - name: Create affiliate program env: AGENTREF_API_KEY: ${{ secrets.AGENTREF_API_KEY }} run: | agentref programs create \ --name "Production Referrals" \ --commission-type recurring \ --commission-percent 20 \ --json ``` For more complex automation workflows, consider using the [Node.js SDK](/docs/agent-integration/node-sdk) or [Python SDK](/docs/agent-integration/python-sdk) instead. The CLI is best for quick operations and shell scripting. # LLMs Context Source: https://agentref.co/docs/agent-integration/llms-context Use llms.txt and llms-full.txt to give AI assistants complete context about the AgentRef documentation. Feed the entire docs into Claude, ChatGPT, Cursor, or any LLM-powered tool. AgentRef publishes machine-readable documentation endpoints that follow the [llms.txt standard](https://llmstxt.org/). These endpoints let you feed the full AgentRef documentation into any AI assistant as context -- enabling more accurate answers, code generation, and integration help. ## Endpoints | URL | Description | Best For | | -------------------------------------------- | ------------------------------------------------------ | ------------------------------------------ | | `https://www.agentref.co/docs/llms.txt` | Concise overview of all documentation pages with links | Quick orientation, smaller context windows | | `https://www.agentref.co/docs/llms-full.txt` | Complete documentation content in plain Markdown | Full context, comprehensive answers | ## What is llms.txt? The `llms.txt` standard provides a way for websites to publish their content in a format optimized for large language models. Instead of making an LLM crawl HTML pages, you give it a single plain-text file containing everything it needs. * **`llms.txt`** -- A structured index of all documentation pages with titles, descriptions, and URLs. Think of it as a table of contents for AI. * **`llms-full.txt`** -- The complete documentation rendered as plain Markdown in a single file. Every page, every code example, every table -- all in one response. ## Using with AI Assistants ### Claude Paste the URL directly in a conversation: ``` Read https://www.agentref.co/docs/llms-full.txt and then help me set up an affiliate program with 20% recurring commission. ``` Or use Claude Projects to add it as a knowledge source: 1. Create a new Claude Project 2. Add `https://www.agentref.co/docs/llms-full.txt` as a knowledge file 3. All conversations in the project will have full AgentRef context ### ChatGPT Use the URL in a prompt or with Browse mode: ``` Fetch https://www.agentref.co/docs/llms-full.txt and use it as context. I need to integrate AgentRef webhooks into my Express.js app. ``` ### Cursor Add the docs to your project context in `.cursorrules` or paste the URL: ``` @https://www.agentref.co/docs/llms-full.txt Help me create a conversion tracking integration using the AgentRef Node SDK. ``` ### Other LLM Tools Any tool that accepts URL context or plain text can use these endpoints. The content is plain Markdown with no special formatting requirements. ## Tips for Optimal Context Usage ### Choose the right endpoint * Use **`llms.txt`** when you only need the AI to know what documentation exists and where to find it. This is lighter on tokens and works well for navigation questions. * Use **`llms-full.txt`** when you need the AI to have deep knowledge of AgentRef's APIs, SDKs, and features. This gives the best answers but uses more context window. ### Scope your questions Even with full documentation context, you'll get better results by being specific: ``` # Good "Using the AgentRef Node SDK, show me how to list pending payouts and create a payout for each affiliate that meets the threshold." # Less good "Help me with AgentRef." ``` ### Combine with code context For integration tasks, give the AI both the AgentRef docs and your own code: ``` I'm building a Next.js app. Here's my current webhook handler: [your code] Read https://www.agentref.co/docs/llms-full.txt and help me verify the webhook signature correctly. ``` ### Use llms.txt for discovery When you are not sure which part of the docs you need, start with `llms.txt`: ``` Read https://www.agentref.co/docs/llms.txt and tell me which pages are relevant for setting up fraud detection. ``` Then follow up with the specific pages or switch to `llms-full.txt` for the complete context. ## Programmatic Access You can fetch these endpoints from your code for automated workflows: ```typescript Node.js theme={null} // Fetch the full docs as context for an LLM call const response = await fetch('https://www.agentref.co/docs/llms-full.txt'); const docsContext = await response.text(); // Use as system prompt context const messages = [ { role: 'system', content: `You are an AgentRef integration assistant. Here is the full documentation:\n\n${docsContext}` }, { role: 'user', content: 'How do I set up webhook signature verification?' } ]; ``` ```python Python theme={null} import httpx # Fetch the full docs as context for an LLM call response = httpx.get("https://www.agentref.co/docs/llms-full.txt") docs_context = response.text # Use as system prompt context messages = [ { "role": "system", "content": f"You are an AgentRef integration assistant. Here is the full documentation:\n\n{docs_context}", }, { "role": "user", "content": "How do I set up webhook signature verification?", }, ] ``` ```bash cURL theme={null} # Download for offline use curl -o agentref-docs.md https://www.agentref.co/docs/llms-full.txt # Quick preview curl -s https://www.agentref.co/docs/llms.txt | head -50 ``` ## Content Updates The llms.txt endpoints reflect the latest published documentation. When docs are updated, the endpoints serve the new content immediately -- no caching delay. Bookmark `https://www.agentref.co/docs/llms-full.txt` in your AI tools. Whenever you're building an AgentRef integration, load it as context for the most accurate, up-to-date assistance. # MCP Server Source: https://agentref.co/docs/agent-integration/mcp-server Connect AI agents to AgentRef through an OAuth-ready MCP server. Run merchant setup, growth, payouts, and affiliate workflows from Claude, Codex, Cursor, or OpenClaw. AgentRef exposes [Model Context Protocol](https://modelcontextprotocol.io) servers for two personas: merchant workflows and affiliate workspace workflows. Use the merchant endpoint for program setup, growth, payouts, applications, and marketing resources. Use the affiliate endpoint for marketplace discovery, links, earnings, payout profile, and affiliate marketing resources. Claude Code, Claude App, Codex, Cursor, and OpenClaw (`mcporter`) can all use the production MCP endpoint directly. For these clients, the recommended path is OAuth-first: add the server, approve the browser sign-in once, and let the client store refreshable credentials for future sessions. ## Endpoint | Persona | Endpoint | | --------- | ------------------------------------------- | | Merchant | `https://www.agentref.co/api/mcp/merchant` | | Affiliate | `https://www.agentref.co/api/mcp/affiliate` | ## Authentication The MCP release contract uses snake\_case inputs and outputs. Mutation tools accept `idempotency_key` when you need retry-safe execution. * **Preferred auth:** OAuth 2.1 with dynamic client registration * **Supported OAuth-first clients:** Claude Code, Claude App, Codex, Cursor, OpenClaw (`mcporter`) * **Fallback auth for custom/manual clients:** `Authorization: Bearer ak_live_*` or `ak_aff_*` If your MCP client supports remote OAuth for Streamable HTTP servers, use that path. If it only supports static headers, create the appropriate API key in **Settings -> API Keys** and attach it manually. ## Setup ```bash theme={null} claude mcp add --scope user --transport http agentref-merchant https://www.agentref.co/api/mcp/merchant ``` Then open `/mcp` in Claude Code, select `agentref`, and authenticate once in the browser. 1. Open `Customize -> Connectors -> + -> Add custom connector` 2. Enter `agentref` as the name 3. Enter `https://www.agentref.co/api/mcp/merchant` as the remote MCP server URL 4. Start a new conversation and send `please make a test call to the agentref mcp` 5. Confirm the tool request, then click **Reconnect** on the AgentRef connector card to authorize access Copy this prompt into Codex: ```text theme={null} Please run `codex mcp add agentref-merchant --url https://www.agentref.co/api/mcp/merchant` in your shell. ``` Codex runs the command for you. Allow command execution once, then complete OAuth in the browser. If AgentRef is already configured and you only need to re-authenticate: ```bash theme={null} codex mcp login agentref-merchant ``` In `Settings -> Tools & MCP -> Add Custom MCP`, save: ```json title=".cursor/mcp.json" theme={null} { "mcpServers": { "agentref": { "url": "https://www.agentref.co/api/mcp/merchant" } } } ``` Then restart Cursor once, open the MCP settings again, and click **Connect** on the AgentRef server card. Install or enable the `mcporter` skill in OpenClaw, then run: ```bash theme={null} mcporter --config ~/.openclaw/workspace/config/mcporter.json config add agentref-merchant https://www.agentref.co/api/mcp/merchant --auth oauth mcporter --config ~/.openclaw/workspace/config/mcporter.json auth --reset agentref-merchant ``` Approve the browser prompt. On success, the callback page says `Authorization successful. You can return to the CLI.` OpenClaw also has a non-MCP fallback: `openclaw skills install agentref` plus `AGENTREF_API_KEY` in `~/.openclaw/.env`. ## Custom clients If your MCP client supports remote OAuth, use the endpoint for the persona you need and complete the browser sign-in flow. If your client only supports manual headers, attach an AgentRef API key: ```text theme={null} Authorization: Bearer ak_live_your_api_key_here ``` ## Workflow groups MCP is the broadest automation surface. Some setup, payout, invite, tracking, and Marketing Resources tools call AgentRef services directly and do not have REST or SDK equivalents yet. Use the REST/SDK pages when you need only the public API v1 surface, and use MCP when an agent needs these expanded workflows. ### Merchant setup tools | Tool | Purpose | | ------------------------- | ------------------------------------------------------------------------- | | `get_onboarding_status` | Read the current merchant onboarding state, blockers, and next steps. | | `update_merchant_profile` | Update company, billing, timezone, and tracking defaults. | | `create_program` | Create a new affiliate program. | | `list_programs` | List programs or read one exact program by `program_id`. | | `update_program` | Update one program's settings, branding, marketplace state, or lifecycle. | | `get_stripe_connect_url` | Generate the Stripe Connect authorization URL for one program. | | `complete_onboarding` | Finalize merchant onboarding when the setup anchors are ready. | | `get_tracking_snippet` | Return the JavaScript snippet and install instructions for one program. | | `verify_tracking` | Check tracking health and return warnings plus next steps. | ### Merchant growth tools | Tool | Purpose | | ---------------------- | -------------------------------------------------------------------------------- | | `list_affiliates` | List affiliates for one merchant, or read one exact affiliate by `affiliate_id`. | | `list_applications` | List marketplace and portal applications, or read one exact application. | | `review_application` | Approve, decline, or block one pending application. | | `set_affiliate_status` | Block or unblock an approved affiliate membership. | | `list_invites` | List invite links for one program. | | `create_invite` | Create a public or targeted invite link. | | `revoke_invite` | Revoke one invite so it cannot be claimed again. | | `get_program_overview` | Load key KPIs, status counts, and warnings for one program. | | `list_conversions` | List merchant conversions with filters. | | `list_flags` | List fraud and review flags, or load one exact flag. | | `review_flag` | Resolve one fraud flag as reviewed, dismissed, or confirmed. | ### Merchant payout tools | Tool | Purpose | | ----------------------- | ------------------------------------------------------------------------ | | `list_pending_payouts` | List payout-ready affiliates who already meet the threshold. | | `list_payouts` | List manual payout records with filters. | | `list_upcoming_payouts` | List affiliates with released unpaid commissions plus next payout date. | | `create_payout` | Create one manual payout record for an affiliate-program pair. | | `update_payout_status` | Advance a payout to `processing`, `completed`, or `failed`. | | `export_payouts_csv` | Export open manual payouts for PayPal, Wise, SEPA, or generic workflows. | ### Merchant marketing resources tools | Tool | Purpose | | --------------------------------------------------- | ------------------------------------------------------------------------------ | | `list_marketing_resources` | List merchant-owned collections, files, social posts, links, and draft assets. | | `get_marketing_resource` | Read one marketing resource or collection. | | `create_marketing_collection` | Create a campaign collection. | | `update_marketing_collection` | Update collection metadata. | | `publish_marketing_resource` | Publish a resource or collection for affiliates. | | `unpublish_marketing_resource` | Move a resource or collection back to draft. | | `archive_marketing_resource` | Archive a resource or collection. | | `create_marketing_social_post` | Create affiliate-ready social post copy. | | `update_marketing_social_post` | Update social post copy, platforms, instructions, or grouping. | | `create_marketing_social_post_media_upload_session` | Create a signed upload session for social post media. | | `complete_marketing_social_post_media_upload` | Attach uploaded media to a social post. | | `remove_marketing_social_post_media` | Remove social post media. | | `update_marketing_social_post_media` | Update media alt text. | | `reorder_marketing_social_post_media` | Normalize social post media order. | | `replace_marketing_social_post_media` | Replace a media file while preserving its media record. | | `create_marketing_resource_download_url` | Create a short-lived merchant download URL. | | `create_marketing_external_link` | Add an external resource link. | | `create_marketing_upload_session` | Create a signed upload session for a file or ZIP import. | | `complete_marketing_upload_session` | Complete an uploaded file or ZIP import. | | `import_marketing_resource_from_url` | Import a remote file or ZIP into private storage. | | `notify_marketing_resource_affiliates` | Queue affiliate notifications for a published resource. | ### Affiliate workspace tools | Tool | Purpose | | -------------------------- | ------------------------------------------------------------------------------- | | `discover_programs` | Search public marketplace programs with filters. | | `apply_to_program` | Apply the current affiliate account to a marketplace program. | | `get_affiliate_overview` | Return aggregate affiliate workspace KPIs across programs. | | `list_my_programs` | List the current affiliate account and linked program context. | | `list_links` | List existing tracking links for the current affiliate account. | | `create_link` | Create one affiliate tracking link for the current program. | | `get_earnings` | Return a compact earnings summary for the current affiliate account. | | `get_click_stats` | Return daily click and unique click totals for the current affiliate account. | | `list_my_payouts` | List payout history for the current affiliate account. | | `update_my_payout_profile` | Update payout destination and billing fields for the current affiliate account. | ### Affiliate marketing resources tools | Tool | Purpose | | ---------------------------------------- | ---------------------------------------------------------------------------- | | `list_marketing_resources` | List affiliate-visible resources for a program. | | `get_marketing_resource` | Read one affiliate-visible resource or collection. | | `render_marketing_social_post` | Render social post copy with affiliate placeholders and disclosure guidance. | | `create_marketing_resource_download_url` | Create a short-lived affiliate download URL. | ## Resources The servers also expose read-oriented resources for structured data access: | Persona | Resource URI | Description | | --------- | ------------------------------------------------- | ------------------------------------------------------------------ | | Merchant | `merchant://me` | Authenticated merchant profile and baseline account configuration. | | Merchant | `program://{id}` | Program configuration and ownership metadata. | | Merchant | `stats://{programId}` | Program-level conversion and commission statistics. | | Merchant | `marketing-resources://program/{program_id}` | Marketing resources for a program. | | Merchant | `marketing-resource-collection://{collection_id}` | One marketing resource collection. | | Merchant | `marketing-resource://{resource_id}` | One marketing resource item. | | Affiliate | `affiliate://me/programs` | Authenticated affiliate program context and performance summary. | | Affiliate | `marketplace://catalog` | Catalog of public marketplace programs. | | Affiliate | `marketing-resources://program/{program_id}` | Affiliate-visible marketing resources for a program. | | Affiliate | `marketing-resource-collection://{collection_id}` | Affiliate-visible collection details. | | Affiliate | `marketing-resource://{resource_id}` | Affiliate-visible resource details. | ## Prompts The MCP server also registers four reusable prompts for guided workflows: | Prompt | Purpose | | ----------------------------- | ---------------------------------------------------------------------------------------------------------- | | `onboard_program` | Guide an agent through program creation, Stripe connect, tracking verification, and onboarding completion. | | `review_payout_run` | Guide a manual payout run from candidate discovery to final payout status updates. | | `debug_tracking_installation` | Diagnose an incomplete or broken tracking installation for one program. | | `triage_fraud_flags` | Review open fraud flags, prioritize them, and resolve them safely. | ## Idempotency Mutation tools accept an optional `idempotency_key`. When provided, the server guarantees at-most-once execution for the logical operation -- safe for agent retries. ``` idempotency_key: "setup-acme-program-2026-04" ``` Always pass an idempotency key when your agent might retry a tool call. This prevents duplicate programs, double payouts, or repeated approvals. ## Workflow Examples ### Set Up a New Program A natural conversation with your AI agent might look like: > **You:** "Create a SaaS affiliate program called 'Acme Pro Referrals' with 25% recurring commission and a 60-day cookie." The agent calls: ``` get_onboarding_status({}) create_program({ name: "Acme Pro Referrals", commission_percent: 25, commission_type: "recurring", cookie_duration: 60, idempotency_key: "create-acme-pro-referrals" }) ``` Then it follows up with `get_stripe_connect_url`, `get_tracking_snippet`, `verify_tracking`, and `complete_onboarding`. ### Monitor Fraud Flags > **You:** "Check if there are any suspicious conversions this week and resolve them." The agent can start with `list_flags`, then resolve specific items with `review_flag`: ``` list_flags({ status: "open", limit: 20 }) review_flag({ flag_id: "flag-uuid-here", resolution: "dismissed", note: "Verified legitimate traffic from blog post", idempotency_key: "resolve-flag-abc123" }) ``` ### Generate a Payout Report > **You:** "Show me who's ready for payout, create the payout records, and export a CSV for PayPal." ``` list_pending_payouts({ program_id: "prog-uuid" }) create_payout({ affiliate_id: "aff-uuid", program_id: "prog-uuid", idempotency_key: "payout-acme-april" }) export_payouts_csv({ format: "paypal", program_id: "prog-uuid" }) ``` ### Affiliate: Discover and Join Programs > **You:** "Find affiliate programs with at least 20% commission and apply to the best one." ``` discover_programs({ min_commission: 20, sort_by: "commission" }) apply_to_program({ program_id: "prog-uuid", message: "I run a SaaS review blog with 50k monthly readers.", idempotency_key: "apply-prog-xyz" }) create_link({ program_id: "prog-uuid", destination_path: "/pricing", custom_slug: "acme-review", idempotency_key: "link-acme-pricing" }) ``` ### Merchant: Publish Campaign Assets > **You:** "Create a LinkedIn post for our launch kit and publish it for affiliates." ``` create_marketing_social_post({ program_id: "prog-uuid", title: "Launch announcement", body: "I am sharing {{program_name}} because it helps teams launch affiliate programs faster: {{affiliate_link}}", platforms: ["linkedin"], status: "draft", idempotency_key: "launch-post-001" }) publish_marketing_resource({ program_id: "prog-uuid", resource_id: "resource-uuid", idempotency_key: "publish-launch-post-001" }) ``` ### Affiliate: Render Campaign Copy > **You:** "Show me the latest social posts for this program and render one with my link." ``` list_marketing_resources({ program_id: "prog-uuid", kind: "social_posts" }) render_marketing_social_post({ program_id: "prog-uuid", resource_id: "resource-uuid", affiliate_link_id: "link-uuid" }) ``` ## Sessions and transport * The endpoints use Streamable HTTP at `https://www.agentref.co/api/mcp/merchant` and `https://www.agentref.co/api/mcp/affiliate` * Sessions are created during the MCP `initialize` flow and handled automatically by clients * If a client holds an expired or invalid session id, it should reconnect and initialize again ## Error Handling Tool errors are returned as structured MCP results with an error code, message, and HTTP status. Common scenarios: | Error | Cause | | ------------------ | ---------------------------------------------------- | | `UNAUTHORIZED` | OAuth not completed yet, or invalid fallback API key | | `FORBIDDEN` | Key lacks required scope | | `NOT_FOUND` | Program, affiliate, or flag does not exist | | `VALIDATION_ERROR` | Invalid input parameters | | `RATE_LIMITED` | Too many requests -- retry after delay | All errors include a `requestId` for debugging. Reference it when contacting support. # Node.js SDK Source: https://agentref.co/docs/agent-integration/node-sdk Official Node.js SDK for the AgentRef REST API. Manage programs, affiliates, conversions, payouts, fraud flags, billing, webhooks, and merchant settings from server-side JavaScript or TypeScript. The `agentref` package is a lightweight, fully-typed Node.js client for the AgentRef REST API v1. It handles authentication, retries, pagination, idempotency, and structured error handling out of the box. ## Installation ```bash theme={null} npm install agentref ``` ## Quick Start ```typescript theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_your_api_key_here', }); // List all programs const { data: programs, meta } = await client.programs.list(); console.log(`Found ${meta.total} programs`); // Get conversion stats const stats = await client.conversions.stats({ period: '30d' }); console.log(`Revenue: $${stats.totalRevenue / 100}`); ``` ## Configuration ```typescript theme={null} const client = new AgentRef({ apiKey: 'ak_live_...', // or set AGENTREF_API_KEY env var baseUrl: 'https://www.agentref.co/api/v1', // default timeout: 30_000, // 30s default maxRetries: 2, // retries for GET + idempotent POST }); ``` | Option | Type | Default | Description | | ------------------------- | --------- | -------------------------------- | ----------------------------------------------- | | `apiKey` | `string` | `AGENTREF_API_KEY` env | Your API key. Required. | | `baseUrl` | `string` | `https://www.agentref.co/api/v1` | API base URL | | `timeout` | `number` | `30000` | Request timeout in ms | | `maxRetries` | `number` | `2` | Max retries for safe/idempotent requests | | `dangerouslyAllowBrowser` | `boolean` | `false` | Override browser safety check (not recommended) | Never expose API keys in client-side code. The SDK throws an error if initialized in a browser context unless you explicitly opt in with `dangerouslyAllowBrowser: true`. ## Resource Namespaces The client exposes the full REST API v1 surface through resource namespaces: | Namespace | Description | | --------------------------- | ----------------------------------------------------------------------------- | | `client.programs` | Create, update, delete, and list affiliate programs | | `client.applications` | Review pending affiliate applications before they become memberships | | `client.affiliates` | List approved memberships, block, and unblock affiliates | | `client.affiliateWorkspace` | Affiliate self-serve overview, programs, links, earnings, payouts, and clicks | | `client.marketingResources` | Merchant and affiliate Marketing Resources workflows | | `client.conversions` | List conversions, get stats, and fetch recent activity | | `client.payouts` | List payouts, pending affiliates, stats, and create payouts | | `client.flags` | List fraud flags, get stats, and resolve flags | | `client.billing` | Check billing status, list tiers, and subscribe | | `client.merchant` | Get and update merchant profile | | `client.notifications` | Get and update notification preferences | | `client.payoutInfo` | Get and update affiliate payout details | | `client.onboarding` | Complete onboarding setup calls | | `client.tracking` | Read program tracking health | | `client.invites` | Inspect and claim public invite links | | `client.marketplace` | Discover marketplace programs and apply as an affiliate | | `client.webhooks` | Create, list, update, delete webhook endpoints and rotate secrets | *** ## Programs ```typescript theme={null} // List programs (paginated) const { data, meta } = await client.programs.list({ status: 'active', limit: 10, page: 1, }); // Auto-paginate through all programs for await (const program of client.programs.listAll()) { console.log(program.name); } // Get program details (includes readiness status) const detail = await client.programs.get('prog-uuid'); console.log(detail.readiness); // 'setup' | 'partial' | 'ready' // Create a program const program = await client.programs.create({ name: 'Acme Referrals', commissionType: 'recurring', commissionPercent: 25, cookieDuration: 60, currency: 'USD', }, { idempotencyKey: 'create-acme-v1' }); // Update a program await client.programs.update('prog-uuid', { commissionPercent: 30, status: 'active', }); // Delete a program await client.programs.delete('prog-uuid'); // Get program stats const stats = await client.programs.stats('prog-uuid', { period: '30d' }); // List program affiliates const affiliates = await client.programs.listAffiliates('prog-uuid', { includeBlocked: false, }); // Manage invites const invite = await client.programs.createInvite('prog-uuid', { email: 'partner@example.com', expiresInDays: 7, }, { idempotencyKey: 'invite-partner-v1' }); const invites = await client.programs.listInvites('prog-uuid'); // Manage coupons const coupon = await client.programs.createCoupon('prog-uuid', { affiliateId: 'aff-uuid', code: 'PARTNER20', }, { idempotencyKey: 'coupon-partner20' }); const coupons = await client.programs.listCoupons('prog-uuid'); await client.programs.deleteCoupon('coupon-uuid'); // Marketplace settings await client.programs.updateMarketplace('prog-uuid', { status: 'public', category: 'SaaS', description: 'Earn 25% recurring for every referral.', }); // Stripe connection const stripe = await client.programs.connectStripe('prog-uuid'); console.log(stripe.authUrl); // redirect merchant to complete OAuth await client.programs.disconnectStripe('prog-uuid'); ``` ## Applications ```typescript theme={null} // Pending joins live in the Applications lifecycle const pending = await client.applications.list({ programId: 'prog-uuid', status: 'pending', }); await client.applications.approve( pending.data[0]!.id, { note: 'Relevant SaaS audience.' }, { idempotencyKey: 'approve-app-123' } ); await client.applications.decline('app-uuid', { note: 'Audience is not a fit.', }); ``` ## Affiliates ```typescript theme={null} // List approved or blocked affiliate memberships const { data } = await client.affiliates.list({ programId: 'prog-uuid', status: 'approved', search: 'john', sortBy: 'totalRevenue', sortOrder: 'desc', limit: 25, }); // Get single affiliate (with optional stats) const affiliate = await client.affiliates.get('aff-uuid', { include: 'stats', }); // Block with reason await client.affiliates.block('aff-uuid', { reason: 'Suspicious click patterns detected', }, { idempotencyKey: 'block-aff-123' }); // Unblock await client.affiliates.unblock('aff-uuid', { idempotencyKey: 'unblock-aff-123', }); ``` ## Affiliate Workspace ```typescript theme={null} const identity = await client.affiliateWorkspace.identity(); const overview = await client.affiliateWorkspace.overview(); const programs = await client.affiliateWorkspace.listPrograms(); const link = await client.affiliateWorkspace.createLink( { name: 'Pricing', destinationPath: '/pricing', customSlug: 'jane-review', }, { programId: programs[0]!.programId, idempotencyKey: 'create-pricing-link', } ); const links = await client.affiliateWorkspace.listLinks({ programId: programs[0]!.programId, }); await client.affiliateWorkspace.updateLink( link.id, { name: 'Pricing - updated', targetUrl: 'https://merchant-site.com/pricing', isActive: true, }, { programId: programs[0]!.programId } ); await client.affiliateWorkspace.deleteLink('old-link-uuid', { programId: programs[0]!.programId, }); const earnings = await client.affiliateWorkspace.earnings(); const programEarnings = await client.affiliateWorkspace.listProgramEarnings( programs[0]!.programId, { period: '30d' } ); const payouts = await client.affiliateWorkspace.listPayouts({ programId: programs[0]!.programId, }); const clicks = await client.affiliateWorkspace.clickStats({ programId: programs[0]!.programId, startDate: '2026-05-01', }); ``` `createLink` uses `destinationPath` and `customSlug`. The current update API accepts `name`, `targetUrl`, and `isActive`; create a new link when you need a different destination path or custom slug. ## Marketing Resources ```typescript theme={null} // Merchant workflow const post = await client.marketingResources.createSocialPost( 'prog-uuid', { title: 'Launch copy', body: 'Try AgentRef: {{affiliate_link}}', platforms: ['linkedin'], status: 'published', }, { idempotencyKey: 'mr-launch-copy' } ); await client.marketingResources.publish( post.id, { programId: 'prog-uuid', notifyAffiliates: true }, { idempotencyKey: 'publish-mr-launch-copy' } ); await client.marketingResources.updateSocialPost(post.id, { programId: 'prog-uuid', title: 'Launch copy v2', }); const upload = await client.marketingResources.createSocialPostMediaUploadSession( post.id, { programId: 'prog-uuid', fileName: 'launch.png', mimeType: 'image/png', fileSizeBytes: 125_000, }, { idempotencyKey: 'launch-media-upload' } ); // Upload the file bytes to upload.signed_url, then attach the uploaded media. await client.marketingResources.completeSocialPostMediaUpload( post.id, { programId: 'prog-uuid', uploadPath: upload.upload_path, uploadToken: upload.upload_token, fileName: 'launch.png', altText: 'Launch product screenshot', }, { idempotencyKey: 'launch-media-complete' } ); await client.marketingResources.createDownloadUrl( { programId: 'prog-uuid', resourceId: post.id }, { idempotencyKey: 'download-launch-copy' } ); // Affiliate workflow const resources = await client.marketingResources.listForAffiliate('prog-uuid', { kind: 'social_posts', }); const resource = await client.marketingResources.getForAffiliate(resources[0]!.id, { programId: 'prog-uuid', }); const rendered = await client.marketingResources.renderSocialPost(resources[0]!.id, { programId: 'prog-uuid', affiliateLinkId: link.id, }); const download = await client.marketingResources.createAffiliateDownloadUrl({ programId: 'prog-uuid', resourceId: resource.id, }); ``` REST/SDK Marketing Resources support social posts, social-post media, publish/unpublish/archive/notify actions, and download URLs. Collection creation, external-link creation, generic file upload sessions, and URL import are currently available through Merchant MCP, not through the REST SDK. | Method | Surface | | ----------------------------------------------------------------------------------------------- | ------------------ | | `list`, `createSocialPost`, `updateSocialPost` | Merchant REST/SDK | | `createSocialPostMediaUploadSession`, `completeSocialPostMediaUpload`, `replaceSocialPostMedia` | Merchant REST/SDK | | `reorderSocialPostMedia`, `updateSocialPostMedia`, `removeSocialPostMedia` | Merchant REST/SDK | | `publish`, `unpublish`, `archive`, `notifyAffiliates`, `createDownloadUrl` | Merchant REST/SDK | | `listForAffiliate`, `getForAffiliate`, `renderSocialPost`, `createAffiliateDownloadUrl` | Affiliate REST/SDK | | Collections, external links, generic file upload, URL import | Merchant MCP only | ## Conversions ```typescript theme={null} // List conversions with filters const { data, meta } = await client.conversions.list({ programId: 'prog-uuid', status: 'pending', startDate: '2026-01-01', endDate: '2026-03-23', limit: 50, }); // Aggregate stats const stats = await client.conversions.stats({ programId: 'prog-uuid', period: '30d', }); console.log(`${stats.total} conversions, $${stats.totalRevenue / 100} revenue`); // Recent conversions const recent = await client.conversions.recent({ limit: 5 }); ``` ## Payouts ```typescript theme={null} // List completed payouts const { data } = await client.payouts.list({ status: 'completed', startDate: '2026-01-01', }); // List pending affiliates (ready for payout) const pending = await client.payouts.listPending({ programId: 'prog-uuid', }); for (const aff of pending.data) { console.log(`${aff.name}: $${aff.pendingAmount / 100} (${aff.currency})`); } // Payout stats const payoutStats = await client.payouts.stats({ period: '30d' }); // Create a payout const payout = await client.payouts.create({ affiliateId: 'aff-uuid', programId: 'prog-uuid', method: 'paypal', notes: 'March payout', }, { idempotencyKey: 'payout-march-aff123' }); ``` ## Fraud Flags ```typescript theme={null} // List open flags const { data: flags } = await client.flags.list({ status: 'open', limit: 20, }); // Get flag stats const flagStats = await client.flags.stats(); console.log(`${flagStats.open} open flags`); // Resolve a flag await client.flags.resolve('flag-uuid', { status: 'dismissed', note: 'Verified legitimate traffic', blockAffiliate: false, }, { idempotencyKey: 'resolve-flag-abc' }); ``` ## Billing ```typescript theme={null} // Check current billing status const billing = await client.billing.current(); console.log(`Tier: ${billing.tier}, Revenue: $${billing.monthlyRevenue / 100}`); // List available tiers const tiers = await client.billing.tiers(); // Subscribe to a tier await client.billing.subscribe({ tier: 'growth', }, { idempotencyKey: 'subscribe-growth-v1' }); ``` ## Merchant ```typescript theme={null} // Get merchant profile const merchant = await client.merchant.get(); // Update merchant settings await client.merchant.update({ companyName: 'Acme Inc.', timezone: 'America/New_York', defaultCookieDuration: 60, }); // Notification preferences const prefs = await client.notifications.get(); await client.notifications.update({ newAffiliate: true, newConversion: true, weeklyDigest: true, }); // Payout info const payoutInfo = await client.payoutInfo.get(); await client.payoutInfo.update({ payoutMethod: 'paypal', paypalEmail: 'payments@acme.com', }); ``` ## Onboarding, Tracking, Invites, and Marketplace ```typescript theme={null} await client.onboarding.upsertMerchantProfile({ companyName: 'Acme Inc.', }); await client.onboarding.complete(); const tracking = await client.tracking.getProgramStatus('prog-uuid'); const invite = await client.invites.get('invite-token'); const claimed = await client.invites.claim(invite.token); const marketplace = await client.marketplace.listPrograms({ sort: 'commission', minCommission: 20, }); await client.marketplace.apply(marketplace.data[0]!.programId!, { message: 'I manage affiliate agents for SaaS products.', }); ``` The REST SDK onboarding namespace currently covers merchant profile upsert by `companyName` and onboarding completion. Merchant MCP has additional setup tools for `get_onboarding_status`, `get_tracking_snippet`, and active tracking verification. ## Webhooks ```typescript theme={null} // List webhook endpoints const endpoints = await client.webhooks.list({ programId: 'prog-uuid', }); // Create endpoint const { endpoint, signingSecret } = await client.webhooks.create({ name: 'Production Webhook', url: 'https://api.acme.com/webhooks/agentref', subscribedEvents: [ 'conversion.created', 'payout.completed', 'affiliate.joined', ], programId: 'prog-uuid', }); // Store signingSecret securely -- it's shown only once // Update endpoint await client.webhooks.update('wh-uuid', { subscribedEvents: [ 'conversion.created', 'conversion.refunded', 'payout.completed', ], }); // Rotate signing secret const { signingSecret: newSecret } = await client.webhooks.rotateSecret('wh-uuid'); // Delete endpoint await client.webhooks.delete('wh-uuid'); ``` *** ## Pagination Paginated REST list methods return a `PaginatedResponse`: ```typescript theme={null} interface PaginatedResponse { data: T[]; meta: { total: number; page: number; pageSize: number; hasMore: boolean; nextCursor?: string; requestId: string; }; } ``` Some convenience list methods return plain arrays because their REST endpoints return array data directly. Examples include `client.affiliateWorkspace.listPrograms()`, `client.affiliateWorkspace.listLinks()`, `client.marketingResources.list()`, `client.marketingResources.listForAffiliate()`, `client.webhooks.list()`, and `client.conversions.recent()`. ### Page-based pagination ```typescript theme={null} const page1 = await client.programs.list({ page: 1, limit: 25 }); if (page1.meta.hasMore) { const page2 = await client.programs.list({ page: 2, limit: 25 }); } ``` ### Auto-pagination The `listAll()` generator handles pagination automatically: ```typescript theme={null} for await (const program of client.programs.listAll({ pageSize: 100 })) { console.log(program.name); } ``` ## Idempotency All mutation methods (POST) accept an `idempotencyKey` option. When provided, the request is safe to retry -- the server guarantees at-most-once execution. ```typescript theme={null} const program = await client.programs.create({ name: 'Acme Referrals', commissionType: 'recurring', commissionPercent: 25, }, { idempotencyKey: 'create-acme-referrals-v1', }); ``` The SDK automatically retries failed requests (up to `maxRetries`) for: * **GET/HEAD requests** -- always safe to retry * **POST requests with an idempotency key** -- server-side deduplication * **429 (rate limited)** and **5xx (server error)** responses POST requests **without** an idempotency key are never retried. ## Error Handling The SDK throws typed errors for all API failures: ```typescript theme={null} import { AgentRefError, AuthError, ForbiddenError, ValidationError, NotFoundError, ConflictError, RateLimitError, ServerError, } from 'agentref'; try { await client.programs.get('nonexistent-id'); } catch (error) { if (error instanceof NotFoundError) { console.log(`Not found: ${error.message}`); console.log(`Request ID: ${error.requestId}`); } else if (error instanceof RateLimitError) { console.log(`Rate limited. Retry after ${error.retryAfter}s`); } else if (error instanceof ValidationError) { console.log(`Validation failed: ${error.message}`); console.log('Details:', error.details); } else if (error instanceof AgentRefError) { console.log(`API error ${error.status}: ${error.code} - ${error.message}`); } } ``` | Error Class | HTTP Status | When | | ----------------- | ----------- | ------------------------------------ | | `AuthError` | 401 | Invalid or missing API key | | `ForbiddenError` | 403 | Key lacks required scope | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Resource does not exist | | `ConflictError` | 409 | Duplicate resource or state conflict | | `RateLimitError` | 429 | Too many requests | | `ServerError` | 5xx | Server-side error | All errors extend `AgentRefError` and include `code`, `status`, `message`, and `requestId` properties. ## TypeScript Types All types are exported from the package: ```typescript theme={null} import type { Program, ProgramDetail, ProgramStats, Application, AffiliateLink, MarketingResource, RenderedMarketingSocialPost, Affiliate, Conversion, ConversionStats, Payout, PendingAffiliate, Flag, FlagStats, BillingStatus, Merchant, WebhookEndpoint, PaginatedResponse, CreateProgramParams, UpdateProgramParams, ResolveFlagParams, CreatePayoutParams, CreateWebhookEndpointParams, // ... and more } from 'agentref'; ``` # Python SDK Source: https://agentref.co/docs/agent-integration/python-sdk Official Python SDK for the AgentRef REST API. Manage programs, affiliates, conversions, payouts, fraud flags, billing, webhooks, and merchant settings with sync and async clients. The `agentref` Python package provides both synchronous and asynchronous clients for the AgentRef REST API v1. It uses [httpx](https://www.python-httpx.org/) under the hood and returns [Pydantic](https://docs.pydantic.dev/) models for core resources. Some flexible surfaces, including Marketing Resources and parts of the affiliate workspace, return dictionaries or lists of dictionaries because their response shape can vary by resource kind. ## Installation ```bash theme={null} pip install agentref ``` ## Quick Start ```python theme={null} from agentref import AgentRef client = AgentRef(api_key="ak_live_your_api_key_here") # List all programs result = client.programs.list() print(f"Found {result.meta.total} programs") # Get conversion stats stats = client.conversions.stats(period="30d") print(f"Revenue: ${stats.total_revenue / 100}") ``` ### Async Usage ```python theme={null} from agentref import AsyncAgentRef async with AsyncAgentRef(api_key="ak_live_...") as client: result = await client.programs.list() for program in result.data: print(program.name) ``` ## Configuration ```python theme={null} client = AgentRef( api_key="ak_live_...", # or set AGENTREF_API_KEY env var base_url="https://www.agentref.co/api/v1", # default timeout=30.0, # 30s default max_retries=2, # retries for GET + idempotent POST ) ``` | Parameter | Type | Default | Description | | ------------- | ------- | -------------------------------- | ---------------------------------------- | | `api_key` | `str` | `AGENTREF_API_KEY` env | Your API key. Required. | | `base_url` | `str` | `https://www.agentref.co/api/v1` | API base URL | | `timeout` | `float` | `30.0` | Request timeout in seconds | | `max_retries` | `int` | `2` | Max retries for safe/idempotent requests | ## Resource Namespaces The client exposes the full REST API v1 surface through resource namespaces: | Namespace | Description | | ---------------------------- | ----------------------------------------------------------------------------- | | `client.programs` | Create, update, delete, and list affiliate programs | | `client.applications` | Review pending affiliate applications before they become memberships | | `client.affiliates` | List approved memberships, block, and unblock affiliates | | `client.affiliate_workspace` | Affiliate self-serve overview, programs, links, earnings, payouts, and clicks | | `client.marketing_resources` | Merchant and affiliate Marketing Resources workflows | | `client.conversions` | List conversions, get stats, and fetch recent activity | | `client.payouts` | List payouts, pending affiliates, stats, and create payouts | | `client.flags` | List fraud flags, get stats, and resolve flags | | `client.billing` | Check billing status, list tiers, and subscribe | | `client.merchant` | Get and update merchant profile | | `client.notifications` | Get and update notification preferences | | `client.payout_info` | Get and update payout payment details | | `client.onboarding` | Complete onboarding setup calls | | `client.tracking` | Read program tracking health | | `client.invites` | Inspect and claim public invite links | | `client.marketplace` | Discover marketplace programs and apply as an affiliate | | `client.webhooks` | Create, list, update, delete webhook endpoints and rotate secrets | *** ## Programs ```python theme={null} # List programs (paginated) result = client.programs.list(status="active", limit=10, page=1) # Auto-paginate through all programs for program in client.programs.list_all(): print(program.name) # Async auto-pagination async for program in client.programs.list_all(): print(program.name) # Get program details (includes readiness status) detail = client.programs.get("prog-uuid") print(detail.readiness) # 'setup' | 'partial' | 'ready' # Create a program program = client.programs.create( name="Acme Referrals", commission_type="recurring", commission_percent=25, cookie_duration=60, currency="USD", idempotency_key="create-acme-v1", ) # Update a program client.programs.update("prog-uuid", commission_percent=30, status="active") # Delete a program client.programs.delete("prog-uuid") # Get program stats stats = client.programs.stats("prog-uuid", period="30d") # List program affiliates affiliates = client.programs.list_affiliates("prog-uuid", include_blocked=False) # Manage invites invite = client.programs.create_invite( "prog-uuid", email="partner@example.com", expires_in_days=7, idempotency_key="invite-partner-v1", ) invites = client.programs.list_invites("prog-uuid") # Manage coupons coupon = client.programs.create_coupon( "prog-uuid", affiliate_id="aff-uuid", code="PARTNER20", idempotency_key="coupon-partner20", ) coupons = client.programs.list_coupons("prog-uuid") client.programs.delete_coupon("coupon-uuid") # Marketplace settings client.programs.update_marketplace( "prog-uuid", status="public", category="SaaS", description="Earn 25% recurring for every referral.", ) # Stripe connection stripe = client.programs.connect_stripe("prog-uuid") print(stripe.auth_url) # redirect merchant to complete OAuth client.programs.disconnect_stripe("prog-uuid") ``` ## Applications ```python theme={null} # Pending joins live in the Applications lifecycle pending = client.applications.list( program_id="prog-uuid", status="pending", ) client.applications.approve( pending.data[0].id, note="Relevant SaaS audience.", idempotency_key="approve-app-123", ) client.applications.decline( "app-uuid", note="Audience is not a fit.", ) ``` ## Affiliates ```python theme={null} # List approved or blocked affiliate memberships result = client.affiliates.list( program_id="prog-uuid", status="approved", search="john", sort_by="totalRevenue", sort_order="desc", limit=25, ) # Get single affiliate (with optional stats) affiliate = client.affiliates.get("aff-uuid", include="stats") # Block with reason client.affiliates.block( "aff-uuid", reason="Suspicious click patterns detected", idempotency_key="block-aff-123", ) # Unblock client.affiliates.unblock("aff-uuid", idempotency_key="unblock-aff-123") ``` ## Affiliate Workspace ```python theme={null} identity = client.affiliate_workspace.identity() overview = client.affiliate_workspace.overview() programs = client.affiliate_workspace.list_programs() link = client.affiliate_workspace.create_link( name="Pricing", destination_path="/pricing", custom_slug="jane-review", program_id=programs[0].program_id, idempotency_key="create-pricing-link", ) links = client.affiliate_workspace.list_links(program_id=programs[0].program_id) client.affiliate_workspace.update_link( link["id"], name="Pricing - updated", target_url="https://merchant-site.com/pricing", is_active=True, program_id=programs[0].program_id, ) client.affiliate_workspace.delete_link( "old-link-uuid", program_id=programs[0].program_id, ) earnings = client.affiliate_workspace.earnings() program_earnings = client.affiliate_workspace.list_program_earnings( programs[0].program_id, period="30d", ) payouts = client.affiliate_workspace.list_payouts( program_id=programs[0].program_id, ) clicks = client.affiliate_workspace.click_stats( program_id=programs[0].program_id, start_date="2026-05-01", ) ``` `create_link` uses `destination_path` and `custom_slug`. The current update API accepts `name`, `target_url`, and `is_active`; create a new link when you need a different destination path or custom slug. ## Marketing Resources ```python theme={null} # Merchant workflow post = client.marketing_resources.create_social_post( "prog-uuid", title="Launch copy", body="Try AgentRef: {{affiliate_link}}", platforms=["linkedin"], status="published", idempotency_key="mr-launch-copy", ) client.marketing_resources.publish( post["id"], program_id="prog-uuid", notify_affiliates=True, idempotency_key="publish-mr-launch-copy", ) client.marketing_resources.update_social_post( post["id"], program_id="prog-uuid", title="Launch copy v2", ) upload = client.marketing_resources.create_social_post_media_upload_session( post["id"], program_id="prog-uuid", file_name="launch.png", mime_type="image/png", file_size_bytes=125_000, idempotency_key="launch-media-upload", ) # Upload the file bytes to upload["signed_url"], then attach the uploaded media. client.marketing_resources.complete_social_post_media_upload( post["id"], program_id="prog-uuid", upload_path=upload["upload_path"], upload_token=upload["upload_token"], file_name="launch.png", alt_text="Launch product screenshot", idempotency_key="launch-media-complete", ) client.marketing_resources.create_download_url( program_id="prog-uuid", resource_id=post["id"], idempotency_key="download-launch-copy", ) # Affiliate workflow resources = client.marketing_resources.list_for_affiliate( "prog-uuid", kind="social_posts", ) resource = client.marketing_resources.get_for_affiliate( resources[0]["id"], program_id="prog-uuid", ) rendered = client.marketing_resources.render_social_post( resources[0]["id"], program_id="prog-uuid", affiliate_link_id=link["id"], ) download = client.marketing_resources.create_affiliate_download_url( program_id="prog-uuid", resource_id=resource["id"], ) ``` REST/SDK Marketing Resources support social posts, social-post media, publish/unpublish/archive/notify actions, and download URLs. Collection creation, external-link creation, generic file upload sessions, and URL import are currently available through Merchant MCP, not through the REST SDK. | Method | Surface | | ----------------------------------------------------------------------------------------------------------- | ------------------ | | `list`, `create_social_post`, `update_social_post` | Merchant REST/SDK | | `create_social_post_media_upload_session`, `complete_social_post_media_upload`, `replace_social_post_media` | Merchant REST/SDK | | `reorder_social_post_media`, `update_social_post_media`, `remove_social_post_media` | Merchant REST/SDK | | `publish`, `unpublish`, `archive`, `notify_affiliates`, `create_download_url` | Merchant REST/SDK | | `list_for_affiliate`, `get_for_affiliate`, `render_social_post`, `create_affiliate_download_url` | Affiliate REST/SDK | | Collections, external links, generic file upload, URL import | Merchant MCP only | ## Conversions ```python theme={null} # List conversions with filters result = client.conversions.list( program_id="prog-uuid", status="pending", start_date="2026-01-01", end_date="2026-03-23", limit=50, ) # Aggregate stats stats = client.conversions.stats(program_id="prog-uuid", period="30d") print(f"{stats.total} conversions, ${stats.total_revenue / 100} revenue") # Recent conversions recent = client.conversions.recent(limit=5) ``` ## Payouts ```python theme={null} # List completed payouts result = client.payouts.list(status="completed", start_date="2026-01-01") # List pending affiliates (ready for payout) pending = client.payouts.list_pending(program_id="prog-uuid") for aff in pending.data: print(f"{aff.name}: ${aff.pending_amount / 100} ({aff.currency})") # Payout stats payout_stats = client.payouts.stats(period="30d") # Create a payout payout = client.payouts.create( affiliate_id="aff-uuid", program_id="prog-uuid", method="paypal", notes="March payout", idempotency_key="payout-march-aff123", ) ``` ## Fraud Flags ```python theme={null} # List open flags result = client.flags.list(status="open", limit=20) # Get flag stats flag_stats = client.flags.stats() print(f"{flag_stats.open} open flags") # Resolve a flag client.flags.resolve( "flag-uuid", status="dismissed", note="Verified legitimate traffic", block_affiliate=False, idempotency_key="resolve-flag-abc", ) ``` ## Billing ```python theme={null} # Check current billing status billing = client.billing.current() print(f"Tier: {billing.tier}, Revenue: ${billing.monthly_revenue / 100}") # List available tiers tiers = client.billing.tiers() # Subscribe to a tier client.billing.subscribe(tier="growth", idempotency_key="subscribe-growth-v1") ``` ## Merchant ```python theme={null} # Get merchant profile merchant = client.merchant.get() # Update merchant settings client.merchant.update( company_name="Acme Inc.", timezone="America/New_York", default_cookie_duration=60, ) ``` ## Notifications ```python theme={null} # Get notification preferences prefs = client.notifications.get() # Update preferences client.notifications.update( new_affiliate=True, new_conversion=True, weekly_digest=True, ) ``` ## Payout Info ```python theme={null} # Get payout info info = client.payout_info.get() # Update payout info client.payout_info.update( payout_method="paypal", paypal_email="payments@acme.com", ) ``` ## Onboarding, Tracking, Invites, and Marketplace ```python theme={null} client.onboarding.upsert_merchant_profile(company_name="Acme Inc.") client.onboarding.complete() tracking = client.tracking.get_program_status("prog-uuid") invite = client.invites.get("invite-token") claimed = client.invites.claim(invite["token"]) marketplace = client.marketplace.list_programs( sort="commission", min_commission=20, ) client.marketplace.apply( marketplace.data[0]["programId"], message="I manage affiliate agents for SaaS products.", ) ``` The REST SDK onboarding namespace currently covers merchant profile upsert by `company_name` and onboarding completion. Merchant MCP has additional setup tools for `get_onboarding_status`, `get_tracking_snippet`, and active tracking verification. ## Webhooks ```python theme={null} # List webhook endpoints endpoints = client.webhooks.list(program_id="prog-uuid") # Create endpoint result = client.webhooks.create( name="Production Webhook", url="https://api.acme.com/webhooks/agentref", subscribed_events=[ "conversion.created", "payout.completed", "affiliate.joined", ], program_id="prog-uuid", ) signing_secret = result.signing_secret # store securely -- shown only once # Update endpoint client.webhooks.update( "wh-uuid", subscribed_events=[ "conversion.created", "conversion.refunded", "payout.completed", ], ) # Rotate signing secret new_result = client.webhooks.rotate_secret("wh-uuid") # Delete endpoint client.webhooks.delete("wh-uuid") ``` *** ## Pagination Paginated REST list methods return a `PaginatedResponse[T]` with typed data and metadata: ```python theme={null} result = client.programs.list(page=1, limit=25) print(result.data) # List[Program] print(result.meta.total) # Total count print(result.meta.has_more) # More pages available? print(result.meta.page) # Current page ``` Some convenience list methods return plain lists because their REST endpoints return array data directly. Examples include `client.affiliate_workspace.list_programs()`, `client.affiliate_workspace.list_links()`, `client.marketing_resources.list()`, `client.marketing_resources.list_for_affiliate()`, `client.webhooks.list()`, and `client.conversions.recent()`. ### Auto-pagination The `list_all()` generator handles pagination automatically: ```python theme={null} # Sync for program in client.programs.list_all(page_size=100): print(program.name) # Async async for program in client.programs.list_all(page_size=100): print(program.name) ``` ## Idempotency POST mutation methods accept an `idempotency_key` keyword argument. When provided, the request is safe to retry -- the server guarantees at-most-once execution. ```python theme={null} program = client.programs.create( name="Acme Referrals", commission_type="recurring", commission_percent=25, idempotency_key="create-acme-referrals-v1", ) ``` The SDK automatically retries failed requests (up to `max_retries`) for: * **GET/HEAD requests** -- always safe to retry * **POST requests with an idempotency key** -- server-side deduplication * **429 (rate limited)** and **5xx (server error)** responses POST requests **without** an idempotency key are never retried. ## Error Handling The SDK raises typed exceptions for all API failures: ```python theme={null} from agentref import ( AgentRefError, AuthError, ForbiddenError, ValidationError, NotFoundError, ConflictError, RateLimitError, ServerError, ) try: client.programs.get("nonexistent-id") except NotFoundError as e: print(f"Not found: {e}") print(f"Request ID: {e.request_id}") except RateLimitError as e: print(f"Rate limited. Retry after {e.retry_after}s") except ValidationError as e: print(f"Validation failed: {e}") print(f"Details: {e.details}") except AgentRefError as e: print(f"API error {e.status}: {e.code} - {e}") ``` | Exception Class | HTTP Status | When | | ----------------- | ----------- | ------------------------------------ | | `AuthError` | 401 | Invalid or missing API key | | `ForbiddenError` | 403 | Key lacks required scope | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Resource does not exist | | `ConflictError` | 409 | Duplicate resource or state conflict | | `RateLimitError` | 429 | Too many requests | | `ServerError` | 5xx | Server-side error | All exceptions inherit from `AgentRefError` and include `code`, `status`, and `request_id` attributes. ## Sync vs Async The Python SDK ships with both synchronous and asynchronous clients: ```python Sync theme={null} from agentref import AgentRef client = AgentRef(api_key="ak_live_...") programs = client.programs.list() client.close() # optional cleanup ``` ```python Async theme={null} from agentref import AsyncAgentRef async with AsyncAgentRef(api_key="ak_live_...") as client: programs = await client.programs.list() # connection pooling managed automatically ``` The async client supports `async with` for automatic connection cleanup. All async resource methods are identical to their sync counterparts but return coroutines. # Block affiliate Source: https://agentref.co/docs/api-reference/affiliates/block-affiliate /openapi.json post /affiliates/{id}/block # Get affiliate by id Source: https://agentref.co/docs/api-reference/affiliates/get-affiliate-by-id /openapi.json get /affiliates/{id} # List affiliates Source: https://agentref.co/docs/api-reference/affiliates/list-affiliates /openapi.json get /affiliates # Unblock affiliate Source: https://agentref.co/docs/api-reference/affiliates/unblock-affiliate /openapi.json post /affiliates/{id}/unblock # Approve application and create affiliate membership Source: https://agentref.co/docs/api-reference/applications/approve-application-and-create-affiliate-membership /openapi.json post /applications/{id}/approve # Block an application Source: https://agentref.co/docs/api-reference/applications/block-an-application /openapi.json post /applications/{id}/block # Decline an application Source: https://agentref.co/docs/api-reference/applications/decline-an-application /openapi.json post /applications/{id}/decline # Get application by id Source: https://agentref.co/docs/api-reference/applications/get-application-by-id /openapi.json get /applications/{id} # List affiliate applications Source: https://agentref.co/docs/api-reference/applications/list-affiliate-applications /openapi.json get /applications # Authentication Source: https://agentref.co/docs/api-reference/authentication How to authenticate with the AgentRef API using API keys. All API requests require authentication via a Bearer token. Your API key acts as the token. ## API Key Format AgentRef uses prefixed API keys to distinguish key types: | Prefix | Type | Description | | ---------- | --------- | ---------------------------------------------------------------------------- | | `ak_live_` | Merchant | Full access to your programs, affiliates, conversions, payouts, and settings | | `ak_aff_` | Affiliate | Access to affiliate self-serve endpoints (`/me/*`, marketplace) | ## Making Requests Include your API key as a Bearer token in the `Authorization` header: ```bash cURL theme={null} curl https://www.agentref.co/api/v1/programs \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ```javascript Node.js theme={null} import { AgentRef } from 'agentref' const client = new AgentRef({ apiKey: 'ak_live_YOUR_KEY' }) const programs = await client.programs.list() ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef(api_key="ak_live_YOUR_KEY") programs = client.programs.list() ``` ## Base URL All API endpoints are served from: ``` https://www.agentref.co/api/v1 ``` ## Scopes API keys are scoped to control access. When creating a key, you choose which scopes to grant. ### Merchant Scopes | Scope | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------- | | `merchant:read` | Read merchant profile and settings | | `merchant:manage` | Update merchant profile and settings | | `programs:read` | List and view programs | | `programs:write` | Create and update programs | | `affiliates:read` | List and view affiliates | | `affiliates:manage` | Review applications and block or unblock approved affiliates | | `conversions:read` | List and view conversions | | `payouts:read` | List and view payouts | | `payouts:write` | Create payouts and mark as completed | | `stats:read` | View program, conversion, and payout statistics | | `billing:read` | View billing information and tiers | | `billing:write` | Subscribe and manage billing | | `flags:read` | List and view fraud flags | | `flags:write` | Resolve fraud flags | | `webhooks:read` | List and view webhook endpoints | | `webhooks:write` | Create, update, and delete webhook endpoints | | `marketing_resources:read` | List, view, and download marketing resources | | `marketing_resources:write` | Create social posts, manage media/status, create download URLs, and notify affiliates about marketing resources | | `invites:claim` | Claim invite tokens | ### Affiliate Scopes | Scope | Description | | -------------------------- | ------------------------------------------------------------------ | | `profile:read` | View own profile | | `profile:write` | Update own profile | | `links:read` | List own tracking links | | `links:write` | Create, update, and delete tracking links | | `earnings:read` | View own commission earnings | | `conversions:read` | View own conversions | | `payouts:read` | View own payout history | | `stats:read` | View own statistics | | `marketplace:read` | Browse the program marketplace | | `marketplace:apply` | Apply to programs | | `marketing_resources:read` | View, render, and download marketing resources for joined programs | | `invites:claim` | Claim invite tokens | ### Scope Presets For convenience, you can use presets when creating API keys: | Preset | Includes | | ---------------- | ------------------------------------------------------- | | `merchant:full` | All merchant scopes | | `affiliate:full` | All affiliate scopes | | `readonly` | All read-only scopes across both merchant and affiliate | ## Creating API Keys 1. Log in to your [AgentRef dashboard](https://www.agentref.co) 2. Go to **Settings → API Keys** 3. Click **Create API Key** 4. Select scopes or a preset 5. Copy the key – it won't be shown again Store your API key securely. Treat it like a password. Never commit API keys to version control or expose them in client-side code. ## Authentication Errors | Status | Code | Description | | ------ | -------------------- | ----------------------------------------------------------------- | | `401` | `UNAUTHORIZED` | Missing or invalid API key | | `403` | `INSUFFICIENT_SCOPE` | API key doesn't have the required scope for this action | | `403` | `FORBIDDEN` | Action not allowed (e.g., accessing another merchant's resources) | # Get billing overview Source: https://agentref.co/docs/api-reference/billing/get-billing-overview /openapi.json get /billing # List available billing tiers Source: https://agentref.co/docs/api-reference/billing/list-available-billing-tiers /openapi.json get /billing/tiers # Start billing subscription checkout Source: https://agentref.co/docs/api-reference/billing/start-billing-subscription-checkout /openapi.json post /billing/subscribe # Get conversion stats Source: https://agentref.co/docs/api-reference/conversions/get-conversion-stats /openapi.json get /conversions/stats # Get recent conversions Source: https://agentref.co/docs/api-reference/conversions/get-recent-conversions /openapi.json get /conversions/recent # List conversions Source: https://agentref.co/docs/api-reference/conversions/list-conversions /openapi.json get /conversions # Delete coupon Source: https://agentref.co/docs/api-reference/coupons/delete-coupon /openapi.json delete /coupons/{id} # Hosted API documentation UI Source: https://agentref.co/docs/api-reference/docs/hosted-api-documentation-ui /openapi.json get /docs # Errors Source: https://agentref.co/docs/api-reference/errors Error response format, HTTP status codes, and rate limiting. The AgentRef API uses standard HTTP status codes and returns structured error responses. ## Error Response Format All errors follow the same structure: ```json theme={null} { "error": { "code": "BAD_REQUEST", "message": "Invalid request. Please check your input", "details": {} }, "meta": { "requestId": "req_abc123" } } ``` | Field | Type | Description | | ---------------- | -------- | -------------------------------------------- | | `error.code` | `string` | Machine-readable error code | | `error.message` | `string` | Human-readable description | | `error.details` | `object` | Additional context (e.g., validation errors) | | `meta.requestId` | `string` | Unique request ID for debugging | ## HTTP Status Codes | Status | Code | Description | | ------ | ------------------------ | ----------------------------------------- | | `400` | `BAD_REQUEST` | Invalid request parameters or body | | `401` | `UNAUTHORIZED` | Missing or invalid API key | | `403` | `FORBIDDEN` | Not allowed to access this resource | | `403` | `INSUFFICIENT_SCOPE` | API key missing required scope | | `404` | `NOT_FOUND` | Resource not found | | `409` | `CONFLICT` | Operation conflicts with existing data | | `409` | `IDEMPOTENCY_KEY_REUSED` | Idempotency key used with different input | | `422` | `VALIDATION_ERROR` | Request body fails validation | | `429` | `RATE_LIMITED` | Too many requests | | `500` | `INTERNAL_ERROR` | Something went wrong on our side | ## Error Handling Example ```javascript Node.js theme={null} import { AgentRef, ForbiddenError, NotFoundError, RateLimitError, } from 'agentref' const client = new AgentRef({ apiKey: 'ak_live_YOUR_KEY' }) try { const program = await client.programs.get('nonexistent') } catch (error) { if (error instanceof NotFoundError) { console.log('Program not found') } else if (error instanceof RateLimitError) { console.log('Rate limited, retry after:', error.retryAfter) } else if (error instanceof ForbiddenError) { console.log('Insufficient permissions') } else { console.error('Unexpected error:', error.message) } } ``` ```python Python theme={null} from agentref import AgentRef from agentref.errors import NotFoundError, RateLimitError, ForbiddenError client = AgentRef(api_key="ak_live_YOUR_KEY") try: program = client.programs.get("nonexistent") except NotFoundError: print("Program not found") except RateLimitError as e: print(f"Rate limited, retry after: {e.retry_after}") except ForbiddenError: print("Insufficient permissions") except Exception as e: print(f"Unexpected error: {e}") ``` ## Rate Limiting API requests are rate limited per API key using a sliding window. ### Rate Limits by Tier | Tier | Requests | Window | | --------------- | -------- | -------- | | Unauthenticated | 10 | 1 minute | | Free | 60 | 1 minute | | Starter | 60 | 1 minute | ### Rate Limit Headers Every response includes rate limit headers: | Header | Description | | ----------------------- | ----------------------------------------- | | `X-RateLimit-Limit` | Maximum requests allowed in the window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | | `Retry-After` | Seconds to wait (only on `429` responses) | ### Handling Rate Limits ```javascript Node.js theme={null} // The AgentRef SDK handles retries automatically const client = new AgentRef({ apiKey: 'ak_live_YOUR_KEY', maxRetries: 3 }) ``` ```python Python theme={null} # The AgentRef SDK handles retries automatically client = AgentRef( api_key="ak_live_YOUR_KEY", max_retries=3 ) ``` ```bash cURL theme={null} # Check the Retry-After header on 429 responses curl -s -D - -H "Authorization: Bearer ak_live_YOUR_KEY" \ https://www.agentref.co/api/v1/programs # HTTP/2 429 # Retry-After: 12 # X-RateLimit-Remaining: 0 ``` # Get fraud flag stats Source: https://agentref.co/docs/api-reference/flags/get-fraud-flag-stats /openapi.json get /flags/stats # List fraud flags Source: https://agentref.co/docs/api-reference/flags/list-fraud-flags /openapi.json get /flags # Resolve fraud flag Source: https://agentref.co/docs/api-reference/flags/resolve-fraud-flag /openapi.json post /flags/{id}/resolve # Idempotency Source: https://agentref.co/docs/api-reference/idempotency How to safely retry API requests without creating duplicate resources. Idempotency lets you safely retry requests without side effects. If a network error occurs and you're unsure whether your request succeeded, you can retry with the same idempotency key. ## How It Works Include an `Idempotency-Key` header with POST requests that create or trigger work. The key lets the server return the original result if your client retries the same request after a timeout or network failure. ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/v1/programs \ -H "Authorization: Bearer ak_live_YOUR_KEY" \ -H "Idempotency-Key: create-acme-referrals-v1" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Referrals", "commissionType": "recurring", "commissionPercent": 25, "currency": "USD" }' ``` ```javascript Node.js theme={null} const program = await client.programs.create({ name: 'Acme Referrals', commissionType: 'recurring', commissionPercent: 25, currency: 'USD', }, { idempotencyKey: 'create-acme-referrals-v1', }) ``` ```python Python theme={null} program = client.programs.create( name="Acme Referrals", commission_type="recurring", commission_percent=25, currency="USD", idempotency_key="create-acme-referrals-v1", ) ``` ## Behavior | Scenario | Result | | ------------------------------------ | ------------------------------------ | | First request with a key | Processed normally, result cached | | Retry with same key + same body | Returns cached result (no duplicate) | | Retry with same key + different body | `409 IDEMPOTENCY_KEY_REUSED` error | | Key expires (after 24 hours) | Treated as a new request | ## Generating Keys Use a UUID v4 or combine business-meaningful values: ```javascript Node.js theme={null} import { randomUUID } from 'crypto' // Option 1: Random UUID const key = randomUUID() // Option 2: Business-meaningful (prevents duplicates across retries AND code paths) const key = `payout_${affiliateId}_${period}` ``` ```python Python theme={null} import uuid # Option 1: Random UUID key = str(uuid.uuid4()) # Option 2: Business-meaningful key = f"payout_{affiliate_id}_{period}" ``` ## Best Practices Idempotency keys are only meaningful for mutation requests that accept them. The Node.js and Python SDKs attach idempotency keys to POST requests when you pass the method-specific idempotency option. `GET`, `PATCH`, and `DELETE` requests are not retried through the idempotency cache. * **Always use idempotency keys** for create/action requests such as creating programs, invites, payouts, or marketing resources * **Use business-meaningful keys** when possible (e.g., `payout_{affiliate_id}_{period}`) to prevent duplicates even across separate code paths * **Retry with the same key** – don't generate a new key for retries * **Keys expire after 24 hours** – after that, the same key can be reused # Claim invite with authenticated API key owner Source: https://agentref.co/docs/api-reference/invites/claim-invite-with-authenticated-api-key-owner /openapi.json post /invites/{token}/claim # Get invite details by public token Source: https://agentref.co/docs/api-reference/invites/get-invite-details-by-public-token /openapi.json get /invites/{token} # Archive a marketing resource Source: https://agentref.co/docs/api-reference/marketing-resources/archive-a-marketing-resource /openapi.json post /marketing-resources/{resourceId}/archive # Complete a social-post media upload Source: https://agentref.co/docs/api-reference/marketing-resources/complete-a-social-post-media-upload /openapi.json post /marketing-resources/social-posts/{resourceId}/media/complete # Create a draft marketing social post Source: https://agentref.co/docs/api-reference/marketing-resources/create-a-draft-marketing-social-post /openapi.json post /programs/{id}/marketing-resources/social-posts # Create a merchant marketing resource download URL Source: https://agentref.co/docs/api-reference/marketing-resources/create-a-merchant-marketing-resource-download-url /openapi.json post /marketing-resources/download-url # Create a social-post media upload session Source: https://agentref.co/docs/api-reference/marketing-resources/create-a-social-post-media-upload-session /openapi.json post /marketing-resources/social-posts/{resourceId}/media/upload-sessions # Create an affiliate marketing resource download URL Source: https://agentref.co/docs/api-reference/marketing-resources/create-an-affiliate-marketing-resource-download-url /openapi.json post /me/marketing-resources/download-url # Get affiliate-visible marketing resource details Source: https://agentref.co/docs/api-reference/marketing-resources/get-affiliate-visible-marketing-resource-details /openapi.json get /me/marketing-resources/{resourceId} # List affiliate-visible marketing resources Source: https://agentref.co/docs/api-reference/marketing-resources/list-affiliate-visible-marketing-resources /openapi.json get /me/programs/{programId}/marketing-resources # List merchant marketing resources Source: https://agentref.co/docs/api-reference/marketing-resources/list-merchant-marketing-resources /openapi.json get /programs/{id}/marketing-resources # Move a marketing resource back to draft Source: https://agentref.co/docs/api-reference/marketing-resources/move-a-marketing-resource-back-to-draft /openapi.json post /marketing-resources/{resourceId}/unpublish # Notify approved affiliates about a marketing resource Source: https://agentref.co/docs/api-reference/marketing-resources/notify-approved-affiliates-about-a-marketing-resource /openapi.json post /marketing-resources/{resourceId}/notify # Publish a marketing resource Source: https://agentref.co/docs/api-reference/marketing-resources/publish-a-marketing-resource /openapi.json post /marketing-resources/{resourceId}/publish # Remove a social-post media attachment Source: https://agentref.co/docs/api-reference/marketing-resources/remove-a-social-post-media-attachment /openapi.json delete /marketing-resources/social-posts/{resourceId}/media/{mediaId} # Render an affiliate social post with available links and disclosure guidance Source: https://agentref.co/docs/api-reference/marketing-resources/render-an-affiliate-social-post-with-available-links-and-disclosure-guidance /openapi.json post /me/marketing-resources/social-posts/{resourceId}/render # Reorder social-post media attachments Source: https://agentref.co/docs/api-reference/marketing-resources/reorder-social-post-media-attachments /openapi.json post /marketing-resources/social-posts/{resourceId}/media/reorder # Replace a social-post media file Source: https://agentref.co/docs/api-reference/marketing-resources/replace-a-social-post-media-file /openapi.json post /marketing-resources/social-posts/{resourceId}/media/{mediaId}/replace # Update a marketing social post Source: https://agentref.co/docs/api-reference/marketing-resources/update-a-marketing-social-post /openapi.json patch /marketing-resources/social-posts/{resourceId} # Update social-post media metadata Source: https://agentref.co/docs/api-reference/marketing-resources/update-social-post-media-metadata /openapi.json patch /marketing-resources/social-posts/{resourceId}/media/{mediaId} # Apply to a marketplace program as authenticated affiliate Source: https://agentref.co/docs/api-reference/marketplace/apply-to-a-marketplace-program-as-authenticated-affiliate /openapi.json post /marketplace/apply/{programId} # Discover public marketplace programs Source: https://agentref.co/docs/api-reference/marketplace/discover-public-marketplace-programs /openapi.json get /marketplace/programs # Create a new affiliate link Source: https://agentref.co/docs/api-reference/me/create-a-new-affiliate-link /openapi.json post /me/links # Delete an affiliate link Source: https://agentref.co/docs/api-reference/me/delete-an-affiliate-link /openapi.json delete /me/links/{id} # Get affiliate program details for the authenticated key Source: https://agentref.co/docs/api-reference/me/get-affiliate-program-details-for-the-authenticated-key /openapi.json get /me/programs/{programId} # Get affiliate workspace overview across programs Source: https://agentref.co/docs/api-reference/me/get-affiliate-workspace-overview-across-programs /openapi.json get /me/overview # Get click stats for authenticated affiliate key Source: https://agentref.co/docs/api-reference/me/get-click-stats-for-authenticated-affiliate-key /openapi.json get /me/clicks # Get current API key identity and owner overview Source: https://agentref.co/docs/api-reference/me/get-current-api-key-identity-and-owner-overview /openapi.json get /me # Get earnings summary for authenticated affiliate key Source: https://agentref.co/docs/api-reference/me/get-earnings-summary-for-authenticated-affiliate-key /openapi.json get /me/earnings # Get payout information for authenticated affiliate key Source: https://agentref.co/docs/api-reference/me/get-payout-information-for-authenticated-affiliate-key /openapi.json get /me/payout-info # List affiliate links for authenticated key Source: https://agentref.co/docs/api-reference/me/list-affiliate-links-for-authenticated-key /openapi.json get /me/links # List earnings entries for a specific program Source: https://agentref.co/docs/api-reference/me/list-earnings-entries-for-a-specific-program /openapi.json get /me/earnings/{programId} # List payouts for authenticated affiliate key Source: https://agentref.co/docs/api-reference/me/list-payouts-for-authenticated-affiliate-key /openapi.json get /me/payouts # List programs for the authenticated affiliate key Source: https://agentref.co/docs/api-reference/me/list-programs-for-the-authenticated-affiliate-key /openapi.json get /me/programs # Update an affiliate link Source: https://agentref.co/docs/api-reference/me/update-an-affiliate-link /openapi.json patch /me/links/{id} # Update payout information for authenticated affiliate key Source: https://agentref.co/docs/api-reference/me/update-payout-information-for-authenticated-affiliate-key /openapi.json patch /me/payout-info # Get merchant account profile Source: https://agentref.co/docs/api-reference/merchant/get-merchant-account-profile /openapi.json get /merchant # Get merchant notification preferences Source: https://agentref.co/docs/api-reference/merchant/get-merchant-notification-preferences /openapi.json get /merchant/notifications # Update merchant account profile Source: https://agentref.co/docs/api-reference/merchant/update-merchant-account-profile /openapi.json patch /merchant # Update merchant notification preferences Source: https://agentref.co/docs/api-reference/merchant/update-merchant-notification-preferences /openapi.json put /merchant/notifications # Finalize onboarding (requires full key and active state) Source: https://agentref.co/docs/api-reference/onboarding/finalize-onboarding-requires-full-key-and-active-state /openapi.json post /onboarding/complete # Set merchant profile data during onboarding Source: https://agentref.co/docs/api-reference/onboarding/set-merchant-profile-data-during-onboarding /openapi.json post /onboarding/merchant # Create manual payout record Source: https://agentref.co/docs/api-reference/payouts/create-manual-payout-record /openapi.json post /payouts # Get payout stats Source: https://agentref.co/docs/api-reference/payouts/get-payout-stats /openapi.json get /payouts/stats # List affiliates eligible for payout Source: https://agentref.co/docs/api-reference/payouts/list-affiliates-eligible-for-payout /openapi.json get /payouts/pending # List payouts Source: https://agentref.co/docs/api-reference/payouts/list-payouts /openapi.json get /payouts # Archive program Source: https://agentref.co/docs/api-reference/programs/archive-program /openapi.json delete /programs/{id} # Connect Stripe for a specific program Source: https://agentref.co/docs/api-reference/programs/connect-stripe-for-a-specific-program /openapi.json post /programs/{id}/connect-stripe # Create coupon for a program Source: https://agentref.co/docs/api-reference/programs/create-coupon-for-a-program /openapi.json post /programs/{id}/coupons # Create invite for a program Source: https://agentref.co/docs/api-reference/programs/create-invite-for-a-program /openapi.json post /programs/{id}/invites # Create program Source: https://agentref.co/docs/api-reference/programs/create-program /openapi.json post /programs # Disconnect Stripe for a specific program Source: https://agentref.co/docs/api-reference/programs/disconnect-stripe-for-a-specific-program /openapi.json delete /programs/{id}/connect-stripe # Get program by id Source: https://agentref.co/docs/api-reference/programs/get-program-by-id /openapi.json get /programs/{id} # Get program stats Source: https://agentref.co/docs/api-reference/programs/get-program-stats /openapi.json get /programs/{id}/stats # Get tracking diagnostics status for a program Source: https://agentref.co/docs/api-reference/programs/get-tracking-diagnostics-status-for-a-program /openapi.json get /programs/{id}/tracking/status # List affiliates for a program Source: https://agentref.co/docs/api-reference/programs/list-affiliates-for-a-program /openapi.json get /programs/{id}/affiliates # List coupons for a program Source: https://agentref.co/docs/api-reference/programs/list-coupons-for-a-program /openapi.json get /programs/{id}/coupons # List invites for a program Source: https://agentref.co/docs/api-reference/programs/list-invites-for-a-program /openapi.json get /programs/{id}/invites # List programs Source: https://agentref.co/docs/api-reference/programs/list-programs /openapi.json get /programs # Update marketplace visibility and listing details for a program Source: https://agentref.co/docs/api-reference/programs/update-marketplace-visibility-and-listing-details-for-a-program /openapi.json patch /programs/{id}/marketplace # Update program Source: https://agentref.co/docs/api-reference/programs/update-program /openapi.json patch /programs/{id} # Create an outbound webhook endpoint Source: https://agentref.co/docs/api-reference/webhooks/create-an-outbound-webhook-endpoint /openapi.json post /webhooks # Disable an outbound webhook endpoint Source: https://agentref.co/docs/api-reference/webhooks/disable-an-outbound-webhook-endpoint /openapi.json delete /webhooks/{id} # Get an outbound webhook endpoint by id Source: https://agentref.co/docs/api-reference/webhooks/get-an-outbound-webhook-endpoint-by-id /openapi.json get /webhooks/{id} # List outbound webhook endpoints for the authenticated merchant Source: https://agentref.co/docs/api-reference/webhooks/list-outbound-webhook-endpoints-for-the-authenticated-merchant /openapi.json get /webhooks # Rotate the signing secret for an outbound webhook endpoint Source: https://agentref.co/docs/api-reference/webhooks/rotate-the-signing-secret-for-an-outbound-webhook-endpoint /openapi.json post /webhooks/{id}/rotate-secret # Update an outbound webhook endpoint Source: https://agentref.co/docs/api-reference/webhooks/update-an-outbound-webhook-endpoint /openapi.json patch /webhooks/{id} # Agent Setup Source: https://agentref.co/docs/getting-started/agent-setup Connect AgentRef to AI assistants via MCP server, Node.js SDK, Python SDK, or REST API – manage your affiliate program through natural language. AgentRef is agent-native. You can manage your affiliate program – creating programs, inviting affiliates, checking stats, processing payouts – through any AI assistant that supports tool use. This page covers all four integration methods. Pick the one that fits your stack. The MCP (Model Context Protocol) server is the fastest way to connect AgentRef to AI assistants. For Claude Code, Claude App, Codex, Cursor, and OpenClaw (`mcporter`), the recommended flow is OAuth-first: add the server once, finish the browser sign-in, and let your client store tokens for future sessions. **Merchant server URL:** `https://www.agentref.co/api/mcp/merchant` **Affiliate server URL:** `https://www.agentref.co/api/mcp/affiliate` **Recommended auth:** MCP OAuth 2.1 with dynamic client registration **Fallback auth for custom clients:** `Authorization: Bearer ak_live_your_key_here` ### Claude Code ```bash theme={null} claude mcp add --scope user --transport http agentref-merchant https://www.agentref.co/api/mcp/merchant ``` Then open `/mcp` in Claude Code, select `agentref`, and complete the OAuth flow once. ### Claude App 1. Open `Customize -> Connectors -> + -> Add custom connector` 2. Enter `agentref` as the name 3. Enter `https://www.agentref.co/api/mcp/merchant` as the remote MCP server URL 4. Start a new conversation and send `please make a test call to the agentref mcp` 5. Confirm the tool request, then click **Reconnect** on the AgentRef connector card to authorize access ### Codex Copy this prompt into Codex: ```text theme={null} Please run `codex mcp add agentref-merchant --url https://www.agentref.co/api/mcp/merchant` in your shell. ``` Codex runs the command for you. Allow command execution once, then complete OAuth in the browser. If AgentRef is already configured and you only need to re-authenticate: ```bash theme={null} codex mcp login agentref-merchant ``` ### Cursor In `Settings -> Tools & MCP -> Add Custom MCP`, save this `mcp.json` entry: ```json theme={null} { "mcpServers": { "agentref": { "url": "https://www.agentref.co/api/mcp/merchant" } } } ``` Then restart Cursor once, open the MCP settings again, and click **Connect** on the AgentRef server card. ### OpenClaw (`mcporter`) Install or enable the `mcporter` skill in OpenClaw, then run: ```bash theme={null} mcporter --config ~/.openclaw/workspace/config/mcporter.json config add agentref-merchant https://www.agentref.co/api/mcp/merchant --auth oauth mcporter --config ~/.openclaw/workspace/config/mcporter.json auth --reset agentref-merchant ``` Approve the browser prompt. On success, the callback page says `Authorization successful. You can return to the CLI.` ### OpenClaw REST skill (fallback) If you prefer OpenClaw without MCP, install the official skill from ClawHub: ```bash theme={null} openclaw skills install agentref ``` Then create the appropriate AgentRef API key and store it in `~/.openclaw/.env` before restarting the gateway: ```bash theme={null} grep -v '^AGENTREF_API_KEY=' ~/.openclaw/.env 2>/dev/null > /tmp/oc_env && printf 'AGENTREF_API_KEY=%s\n' 'ak_live_your_key_here' >> /tmp/oc_env && mv /tmp/oc_env ~/.openclaw/.env && openclaw gateway restart ``` ### Other MCP clients Any remote MCP client that supports OAuth for Streamable HTTP servers can use the same endpoint. Prefer OAuth when available. If your client only supports manual headers, use the same endpoint with `Authorization: Bearer ak_live_your_key_here`. ### Try it out After connecting, try these prompts with your AI assistant: * "Check my onboarding status and tell me what is still blocking launch" * "Show me my affiliate program stats for the last 30 days" * "List all my affiliates and their performance" * "Create a new affiliate program called 'Partner Program' with 25% recurring commission" * "Check if my tracking script is installed correctly" The Node.js SDK provides a type-safe client for all AgentRef API operations. ### Installation ```bash theme={null} npm install agentref ``` ### Configuration ```typescript theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_your_key_here', }); ``` Store your API key in environment variables, not in source code. Use `process.env.AGENTREF_API_KEY` in production. ### Create a program ```typescript theme={null} const program = await client.programs.create({ name: 'Partner Program', commissionType: 'recurring', commissionPercent: 25, cookieDuration: 30, payoutThreshold: 5000, // $50.00 in cents payoutFrequency: 'monthly', autoApproveAffiliates: true, }); console.log(`Program created: ${program.id}`); ``` ### Invite an affiliate ```typescript theme={null} const invite = await client.programs.createInvite(program.id, { email: 'partner@example.com', }); console.log(`Invite sent: ${invite.id}`); ``` ### Check stats ```typescript theme={null} const stats = await client.programs.stats(program.id, { period: '30d', }); console.log(`Conversions: ${stats.total}`); console.log(`Revenue: $${(stats.totalRevenue / 100).toFixed(2)}`); ``` ### List conversions ```typescript theme={null} const conversions = await client.conversions.list({ programId: program.id, status: 'pending', limit: 20, }); for (const conversion of conversions.data) { console.log(`${conversion.id}: $${(conversion.amount / 100).toFixed(2)}`); } ``` For the full SDK reference, see [Node.js SDK](/docs/agent-integration/node-sdk). The Python SDK provides a clean client for all AgentRef API operations. ### Installation ```bash theme={null} pip install agentref ``` ### Configuration ```python theme={null} from agentref import AgentRef client = AgentRef(api_key='ak_live_your_key_here') ``` Store your API key in environment variables, not in source code. Use `os.environ['AGENTREF_API_KEY']` in production. ### Create a program ```python theme={null} program = client.programs.create( name='Partner Program', commission_type='recurring', commission_percent=25, cookie_duration=30, payout_threshold=5000, # $50.00 in cents payout_frequency='monthly', auto_approve_affiliates=True, ) print(f"Program created: {program.id}") ``` ### Invite an affiliate ```python theme={null} invite = client.programs.create_invite( program.id, email='partner@example.com', ) print(f"Invite sent: {invite.id}") ``` ### Check stats ```python theme={null} stats = client.programs.stats( program.id, period='30d', ) print(f"Conversions: {stats.total}") print(f"Revenue: ${stats.total_revenue / 100:.2f}") ``` ### List conversions ```python theme={null} conversions = client.conversions.list( program_id=program.id, status='pending', limit=20, ) for conversion in conversions.data: print(f"{conversion.id}: ${conversion.amount / 100:.2f}") ``` For the full SDK reference, see [Python SDK](/docs/agent-integration/python-sdk). If you're not using an SDK or MCP, you can call the REST API directly. **Base URL:** `https://www.agentref.co/api/v1` **Auth:** Pass your API key as a Bearer token in the `Authorization` header. ### List programs ```bash cURL theme={null} curl -X GET https://www.agentref.co/api/v1/programs \ -H "Authorization: Bearer ak_live_your_key_here" ``` ```javascript fetch theme={null} const res = await fetch('https://www.agentref.co/api/v1/programs', { headers: { Authorization: 'Bearer ak_live_your_key_here' }, }); const data = await res.json(); ``` ```python requests theme={null} import requests res = requests.get( 'https://www.agentref.co/api/v1/programs', headers={'Authorization': 'Bearer ak_live_your_key_here'}, ) data = res.json() ``` ### Create a program ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/v1/programs \ -H "Authorization: Bearer ak_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Partner Program", "commissionType": "recurring", "commissionPercent": 25, "cookieDuration": 30, "payoutThreshold": 5000, "payoutFrequency": "monthly", "autoApproveAffiliates": true }' ``` ```javascript fetch theme={null} const res = await fetch('https://www.agentref.co/api/v1/programs', { method: 'POST', headers: { Authorization: 'Bearer ak_live_your_key_here', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Partner Program', commissionType: 'recurring', commissionPercent: 25, cookieDuration: 30, payoutThreshold: 5000, payoutFrequency: 'monthly', autoApproveAffiliates: true, }), }); const program = await res.json(); ``` ```python requests theme={null} import requests res = requests.post( 'https://www.agentref.co/api/v1/programs', headers={ 'Authorization': 'Bearer ak_live_your_key_here', 'Content-Type': 'application/json', }, json={ 'name': 'Partner Program', 'commissionType': 'recurring', 'commissionPercent': 25, 'cookieDuration': 30, 'payoutThreshold': 5000, 'payoutFrequency': 'monthly', 'autoApproveAffiliates': True, }, ) program = res.json() ``` ### Get stats ```bash cURL theme={null} curl -X GET https://www.agentref.co/api/v1/programs/PROGRAM_ID/stats \ -H "Authorization: Bearer ak_live_your_key_here" ``` ```javascript fetch theme={null} const res = await fetch('https://www.agentref.co/api/v1/programs/PROGRAM_ID/stats', { headers: { Authorization: 'Bearer ak_live_your_key_here' }, }); const programStats = await res.json(); ``` ```python requests theme={null} res = requests.get( 'https://www.agentref.co/api/v1/programs/PROGRAM_ID/stats', headers={'Authorization': 'Bearer ak_live_your_key_here'}, ) program_stats = res.json() ``` For the complete API reference, see [API Reference](/docs/api-reference/authentication). ## Copy for AI assistants If your AI assistant does not support MCP, point it to `https://www.agentref.co/docs/llms-full.txt` first. That gives it the full published AgentRef documentation as plain Markdown. ```text theme={null} AgentRef docs: https://www.agentref.co/docs Full docs for LLMs: https://www.agentref.co/docs/llms-full.txt REST base URL: https://www.agentref.co/api/v1 MCP merchant endpoint: https://www.agentref.co/api/mcp/merchant MCP affiliate endpoint: https://www.agentref.co/api/mcp/affiliate Preferred MCP auth: OAuth 2.1 + dynamic client registration Fallback MCP auth: Authorization: Bearer ak_live_* / ak_aff_* Current MCP workflow groups: - merchant_setup: get_onboarding_status, update_merchant_profile, create_program, list_programs, update_program, get_stripe_connect_url, complete_onboarding, get_tracking_snippet, verify_tracking - merchant_growth: list_affiliates, list_applications, review_application, set_affiliate_status, list_invites, create_invite, revoke_invite, get_program_overview, list_conversions, list_flags, review_flag - merchant_payouts: list_pending_payouts, list_payouts, list_upcoming_payouts, create_payout, update_payout_status, export_payouts_csv - merchant_marketing_resources: list_marketing_resources, get_marketing_resource, create_marketing_collection, update_marketing_collection, publish_marketing_resource, unpublish_marketing_resource, archive_marketing_resource, create_marketing_social_post, update_marketing_social_post, create_marketing_social_post_media_upload_session, complete_marketing_social_post_media_upload, remove_marketing_social_post_media, update_marketing_social_post_media, reorder_marketing_social_post_media, replace_marketing_social_post_media, create_marketing_resource_download_url, create_marketing_external_link, create_marketing_upload_session, complete_marketing_upload_session, import_marketing_resource_from_url, notify_marketing_resource_affiliates - affiliate_workspace: discover_programs, apply_to_program, get_affiliate_overview, list_my_programs, list_links, create_link, get_earnings, get_click_stats, list_my_payouts, update_my_payout_profile - affiliate_marketing_resources: list_marketing_resources, get_marketing_resource, render_marketing_social_post, create_marketing_resource_download_url Resources: merchant://me program://{id} stats://{programId} affiliate://me/programs marketplace://catalog marketing-resources://program/{program_id} marketing-resource-collection://{collection_id} marketing-resource://{resource_id} Prompts: onboard_program review_payout_run debug_tracking_installation triage_fraud_flags ``` ## MCP workflow groups AgentRef's MCP release surface is organized into workflow groups with snake\_case inputs and outputs. Mutation tools accept `idempotency_key` when retries need to be safe. MCP includes some workflows that are broader than REST/SDK, especially onboarding status, tracking snippet/verify, invite revoke, payout exports/status updates, and advanced Marketing Resources operations. ### `merchant_setup` * `get_onboarding_status` * `update_merchant_profile` * `create_program` * `list_programs` * `update_program` * `get_stripe_connect_url` * `complete_onboarding` * `get_tracking_snippet` * `verify_tracking` ### `merchant_growth` * `list_affiliates` * `list_applications` * `review_application` * `set_affiliate_status` * `list_invites` * `create_invite` * `revoke_invite` * `get_program_overview` * `list_conversions` * `list_flags` * `review_flag` ### `merchant_payouts` * `list_pending_payouts` * `list_payouts` * `list_upcoming_payouts` * `create_payout` * `update_payout_status` * `export_payouts_csv` ### `merchant_marketing_resources` * `list_marketing_resources` * `get_marketing_resource` * `create_marketing_collection` * `update_marketing_collection` * `publish_marketing_resource` * `unpublish_marketing_resource` * `archive_marketing_resource` * `create_marketing_social_post` * `update_marketing_social_post` * `create_marketing_social_post_media_upload_session` * `complete_marketing_social_post_media_upload` * `remove_marketing_social_post_media` * `update_marketing_social_post_media` * `reorder_marketing_social_post_media` * `replace_marketing_social_post_media` * `create_marketing_resource_download_url` * `create_marketing_external_link` * `create_marketing_upload_session` * `complete_marketing_upload_session` * `import_marketing_resource_from_url` * `notify_marketing_resource_affiliates` ### `affiliate_workspace` * `discover_programs` * `apply_to_program` * `get_affiliate_overview` * `list_my_programs` * `list_links` * `create_link` * `get_earnings` * `get_click_stats` * `list_my_payouts` * `update_my_payout_profile` ### `affiliate_marketing_resources` * `list_marketing_resources` * `get_marketing_resource` * `render_marketing_social_post` * `create_marketing_resource_download_url` See [MCP Server Deep Dive](/docs/agent-integration/mcp-server) for the full tool, resource, prompt, and setup reference. ## What's next Advanced MCP configuration, scopes, and resource access. Full SDK reference with all methods and types. Full SDK reference for Python integrations. # How It Works Source: https://agentref.co/docs/getting-started/how-it-works The full attribution chain – from affiliate click to conversion, commission calculation, fraud detection, and payout. AgentRef handles the complete affiliate lifecycle: tracking clicks, attributing conversions, calculating commissions, detecting fraud, and managing payouts. This page walks through each stage. ## The attribution chain Every conversion follows this path: ```mermaid theme={null} graph TD A["Affiliate shares referral link"] --> B["Visitor clicks link"] B --> C["AgentRef records click + sets cookie"] C --> D["Visitor browses your site"] D --> E["Visitor completes purchase via Stripe"] E --> F["Stripe webhook fires"] F --> G["AgentRef matches cookie to affiliate"] G --> H["Conversion created (pending)"] H --> I["Commission calculated"] I --> J["Fraud checks run"] J --> K["Commission approved"] K --> L["Payout when threshold reached"] ``` ## Click tracking When a visitor clicks an affiliate's referral link, AgentRef's tracking script handles attribution automatically: 1. **Referral link redirect.** The visitor clicks a link like `https://www.agentref.co/r/abc123`. AgentRef resolves the affiliate code, records the click, and redirects to the target URL. 2. **Cookie placement.** The tracking script sets first-party cookies on your domain: * `agentref_cid` – a click identifier for conversion matching * `agentref_src` – the traffic source * `agentref_pid` – the active program ID 3. **Cookie duration.** Cookies persist for your configured duration (default: 30 days). If a visitor returns within that window and converts, the affiliate still gets credit. 4. **Click deduplication.** Multiple clicks from the same visitor within a short window are deduplicated to prevent inflated metrics. The tracking script is a lightweight, first-party JavaScript snippet that runs on your domain. It does not use third-party cookies or fingerprinting. ## Conversion attribution When a tracked visitor makes a purchase, AgentRef connects the sale to the affiliate: 1. **Stripe webhook.** After a successful payment, Stripe sends a `checkout.session.completed` (or `invoice.paid` for subscriptions) webhook to AgentRef. 2. **Metadata matching.** AgentRef looks for the `agentref_cid` in the Stripe session metadata. This links the payment back to the original click. 3. **Conversion record.** A conversion is created with status `pending`. It includes the revenue amount, the affiliate ID, and the click that drove the sale. 4. **Last-click attribution.** If multiple affiliates drove clicks for the same visitor, the most recent click before conversion wins. This is the industry-standard attribution model. ### How metadata flows into Stripe For **Stripe Payment Links, Buy Buttons, and Pricing Tables**, the tracking script automatically instruments the hosted Stripe surface with the click token via `client_reference_id` – no server code needed. For **server-created Checkout Sessions**, you read the metadata from the tracking script on the client side and pass it to your server: ```javascript theme={null} // Client side – read captured referral context const metadata = window.AgentRef.getCheckoutMetadata(); // Returns: { agentref_cid: "clk_abc123", agentref_pid: "550e8400-e29b-41d4-a716-446655440000", agentref_source: "redirect" } // Server side – include in Stripe session creation const session = await stripe.checkout.sessions.create({ // ...your config metadata: metadata, subscription_data: { metadata: metadata }, // for subscriptions }); ``` ## Commission calculation Once a conversion is recorded, AgentRef calculates the affiliate's commission based on your program settings: | Commission type | How it works | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **Percentage (one-time)** | Affiliate earns a percentage of the sale amount. Example: 20% of a $100 sale = $20 commission. | | **Percentage (recurring)** | Affiliate earns a percentage on every subscription payment. Continues for the lifetime of the subscription or until a configured limit. | | **Recurring with limit** | Same as recurring, but capped at a number of months. Example: 20% for the first 12 months. | Commissions are calculated in the currency of the Stripe payment. AgentRef supports multi-currency programs. ## Fraud detection Every conversion passes through automated fraud checks before commissions are approved: * **Duplicate click detection.** Flags unusual click patterns from the same IP or device within a short timeframe. * **Self-referral detection.** Catches affiliates who try to refer themselves by matching affiliate identity against the buyer. * **Suspicious conversion patterns.** Identifies statistically unusual spikes in conversions that deviate from an affiliate's normal baseline. * **Geographic anomalies.** Flags when click geography and conversion geography don't match expected patterns. Flagged conversions are held for review. You can resolve flags through the dashboard, API, or MCP server using the `review_flag` tool. Most legitimate conversions pass fraud checks automatically. Flags are designed to surface genuine anomalies, not to create false positives for normal affiliate activity. ## Payouts When an affiliate's approved commissions reach the payout threshold, they become eligible for payout: 1. **Threshold check.** Default payout threshold is \$50 (configurable per program). Commissions accumulate until this amount is reached. 2. **Payout schedule.** Payouts are processed on your configured frequency: weekly, monthly, or quarterly. 3. **Payout execution.** AgentRef creates the payout record and handles the commission state transitions. You complete the actual payment through your preferred method (Stripe, PayPal, Wise, bank transfer). 4. **CSV export.** For bulk payouts, export a CSV in PayPal, Wise, SEPA, or generic format using the MCP `export_payouts_csv` tool. ### Conversion status flow Every conversion moves through a defined lifecycle: ```mermaid theme={null} graph LR A[pending] --> B[approved] A --> C[rejected] B --> D[paid] A --> E[refunded] B --> F[partially_refunded] F --> E B --> E ``` * **Pending** – Conversion recorded, awaiting fraud checks and approval window. * **Approved** – Passed all checks. Commission is locked in and counts toward payout. * **Rejected** – Failed fraud checks or manually rejected by merchant. * **Paid** – Commission has been included in a completed payout. * **Partially refunded** – Part of the Stripe payment was refunded. Commission is reduced proportionally. * **Refunded** – The original Stripe payment was fully refunded. Commission is reversed. ## Integration points AgentRef provides multiple ways to integrate with your stack: | Integration | Purpose | Best for | | ----------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | **Tracking script** | Captures clicks and sets attribution cookies on your domain. | All merchants – required for click tracking. | | **Stripe webhooks** | Automatically creates conversions from Stripe payment events. | All merchants using Stripe. | | **REST API** | Full programmatic access to all AgentRef resources. | Custom integrations, backend automation. | | **MCP server** | OAuth-ready tools, resources, and prompts for AI assistants. | Claude, Codex, Cursor, OpenClaw, and custom agent workflows. | | **Webhooks (outgoing)** | Receive real-time notifications for 13 event types (program, affiliate, conversion, payout, flag events). | Custom workflows, CRM sync, notifications. | | **Node.js SDK** | Type-safe API client for JavaScript/TypeScript projects. | Node.js backends and scripts. | | **Python SDK** | API client for Python projects. | Python backends, data analysis, automation scripts. | ## Next steps Set up your first program in 5 minutes. Connect AgentRef to your AI assistant via MCP. # Quickstart Source: https://agentref.co/docs/getting-started/quickstart Set up your first AgentRef affiliate program in under 5 minutes – from account creation to your first API call. This guide walks you through setting up a complete affiliate program. By the end, you'll have tracking installed, an API key ready, and a working integration. ## Prerequisites * A [Stripe account](https://stripe.com) (for payment tracking) * A website where you sell your product (for the tracking script) ## Setup Sign up at [agentref.co](https://www.agentref.co). No credit card required. After signing up, you'll land on the onboarding flow that walks you through the next steps. Click **Connect Stripe** in the onboarding flow. You'll be redirected to Stripe's OAuth page to authorize AgentRef. AgentRef uses Stripe Connect to: * Listen for payment events (checkout completions, invoice payments, refunds) * Match payments to affiliate referrals * Track subscription renewals for recurring commissions AgentRef needs read access to your Stripe payment events. It does not process payments or access your bank account. After connecting Stripe, create your first affiliate program. Here are recommended starting settings: | Setting | Recommendation | Why | | --------------------------- | -------------- | ---------------------------------------------------------------------------- | | **Commission type** | `recurring` | Incentivizes affiliates to refer customers who stick around. | | **Commission rate** | 20–30% | Industry standard for SaaS. Competitive enough to attract affiliates. | | **Cookie duration** | 30 days | Balances fair attribution with reasonable conversion windows. | | **Payout threshold** | \$50 | Low enough to keep affiliates motivated, high enough to avoid micro-payouts. | | **Payout frequency** | Monthly | Standard cadence. Weekly is fine for high-volume programs. | | **Auto-approve affiliates** | Yes | Reduces friction. You can always block bad actors later. | You can change all of these settings later. Start simple and adjust based on real data. Add the AgentRef tracking script to your website. Place it in your `` tag or before the closing `` tag: ```html theme={null} ``` Replace `YOUR_PROGRAM_ID` with the ID shown in your program settings. The script handles: * Capturing affiliate referral codes from URL parameters * Setting first-party attribution cookies (`agentref_cid`, `agentref_pid`, `agentref_src`) * Providing `getCheckoutMetadata()` for Stripe session integration **For Stripe Payment Links / Buy Buttons / Pricing Tables:** No additional code needed. The tracking script instruments hosted Stripe surfaces with the click token via `client_reference_id`. **For server-created Checkout Sessions:** Pass the metadata to your server: ```javascript Client-side theme={null} // Read referral context captured by the tracking script const metadata = window.AgentRef.getCheckoutMetadata(); // Send metadata to your server along with the checkout request ``` ```javascript Server-side (Node.js) theme={null} const session = await stripe.checkout.sessions.create({ line_items: [{ price: 'price_xxx', quantity: 1 }], mode: 'subscription', metadata: metadata, // top-level metadata subscription_data: { metadata: metadata }, // subscription metadata success_url: 'https://yoursite.com/success', cancel_url: 'https://yoursite.com/cancel', }); ``` Go to **Settings > API Keys** in your AgentRef dashboard and create a new key. * **Merchant keys** start with `ak_live_` and can access your programs, applications, affiliates, conversions, payouts, webhooks, and marketing resources based on selected scopes. Keep your API key secret. Never expose it in client-side code, public repositories, or browser requests. Verify everything works by fetching your program stats: ```bash cURL theme={null} curl -X GET https://www.agentref.co/api/v1/programs \ -H "Authorization: Bearer ak_live_your_key_here" ``` ```javascript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_your_key_here' }); const programs = await client.programs.list(); console.log(programs); ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef(api_key='ak_live_your_key_here') programs = client.programs.list() print(programs) ``` You should see your program in the response. If you get a `401` error, double-check your API key. ## Verify your setup After completing the steps above, confirm everything is working: 1. **Tracking script loaded.** Open your website, open browser DevTools, and check the Network tab for a request to `agentref.co/api/tracking/script.js`. It should return `200`. 2. **Cookies set.** Visit your site through a test referral link (create one in the dashboard). Check DevTools > Application > Cookies for `agentref_cid` and `agentref_pid`. 3. **API responds.** The API call from the previous step returns your program data. 3. **Stripe connected.** Your AgentRef dashboard shows the Stripe connection as active. Use the Merchant MCP `verify_tracking` tool for an agent-assisted setup check, or call `GET /api/v1/programs/{id}/tracking/status` to inspect tracking health from REST/SDK. ## What's next Connect AgentRef to Claude, Codex, Cursor, OpenClaw, or any AI assistant. Start recruiting affiliates to your program. Publish campaign material for affiliates. Explore the full REST API documentation. # Welcome to AgentRef Source: https://agentref.co/docs/getting-started/welcome Affiliate tracking for SaaS – agent-compatible from day one.
Pay nothing until you earn. AgentRef is affiliate tracking software for SaaS. It connects your Stripe account, tracks referral clicks, attributes conversions, and pays affiliates – all with zero upfront cost. Start free, then upgrade when your program needs higher limits.
No per-seat pricing. ## Get started Set up your first affiliate program in 5 minutes. Connect AgentRef to Claude, Codex, Cursor, OpenClaw, or any AI agent via MCP. Explore the REST API, SDKs, and authentication. ## Why AgentRef? Most affiliate platforms charge \$50–500/month before you have a single affiliate. AgentRef flips that model: * **\$0 to start.** No credit card required. Create your program, invite affiliates, and go live immediately. * **Program-friendly pricing.** Start free and move to a paid plan when your affiliate program needs higher limits. * **Agent-compatible.** Manage your affiliate program through AI assistants using the MCP server, SDKs, or REST API when it fits your workflow. ## Core concepts Before diving in, here are the terms you'll see throughout these docs: | Concept | What it means | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Program** | Your affiliate program. It defines commission rates, cookie duration, payout rules, and tracking settings. One merchant can run multiple programs. | | **Affiliate** | A partner who promotes your product using unique referral links. They earn commissions on conversions they drive. | | **Click** | A tracked visit through an affiliate's referral link. AgentRef records the click and drops a first-party cookie for attribution. | | **Conversion** | A successful sale or signup attributed to an affiliate. Created automatically from supported attribution sources such as Stripe metadata, Stripe `client_reference_id`, or coupon attribution. | | **Commission** | The amount an affiliate earns for a conversion. Can be a percentage of revenue, a fixed amount, or recurring for subscription products. | | **Payout** | The transfer of accumulated commissions to an affiliate once they hit the payout threshold. | | **Marketing Resource** | Merchant-published campaign material such as files, social posts, swipe copy, external links, and collections. | ## How it fits together ```mermaid theme={null} graph LR A[Affiliate shares link] --> B[Visitor clicks link] B --> C[AgentRef tracks click] C --> D[Visitor buys on your site] D --> E[Stripe webhook fires] E --> F[AgentRef creates conversion] F --> G[Commission attributed] G --> H[Payout when threshold met] ``` 1. You create a **program** with your commission structure. 2. **Affiliates** join and share referral links. 3. AgentRef's tracking script captures **clicks** and sets attribution cookies. 4. When a visitor converts (via Stripe), AgentRef matches the **conversion** to the affiliate. 5. **Commissions** accumulate until the payout threshold is reached. 6. **Payouts** are processed on your chosen schedule. ## What makes AgentRef different OAuth-ready MCP server with setup, growth, payout, marketing resources, and affiliate workspace tools. Your AI assistant can operate AgentRef through natural language. No monthly fees, no setup costs. You only pay when affiliates actually drive revenue for your business. Connects directly to your Stripe account. Conversions are tracked automatically through webhooks – no manual reporting. Supports recurring commissions for subscriptions, handles refunds and chargebacks, and scales from day one. ## Next steps Understand the full attribution chain from click to payout. Get your first program running in under 5 minutes. ## Choose your integration surface | Surface | Use it when | Notes | | ---------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Dashboard | You are launching or operating a program manually. | Best for setup, visual review, billing, and day-to-day operations. | | REST API | You are building a server-side integration. | Stable public API v1 with OpenAPI reference, API keys, scopes, idempotency, and webhooks. | | Node/Python SDKs | You want typed REST clients in application code. | Mirrors the REST API surface and handles auth, retries, errors, and pagination helpers. | | MCP Server | You want an AI agent to run AgentRef workflows. | Broadest automation surface; includes setup, payout, and Marketing Resources workflows that are not all exposed in REST/SDK yet. | If you are unsure, start with the dashboard and tracking quickstart. Use REST or an SDK for app code, and MCP when an AI assistant should operate AgentRef directly. # Astro Integration Source: https://agentref.co/docs/integration/astro Install AgentRef in a shared Astro layout so affiliate tracking works across your marketing site or app shell. Astro is a good fit for AgentRef because the tracking script can live in one shared layout and still cover static pages, islands, and Stripe embeds. ## Before you start * Set the program website in AgentRef to the same root domain your Astro site uses. * Copy the exact script snippet from `Settings -> Integration`. * If checkout starts on a sibling subdomain, install the script there too. ## Recommended installation Put the script in the shared layout you use across public pages. ```astro theme={null} --- export interface Props { title: string; } const { title } = Astro.props; --- {title} ``` You do not need a separate Framer-style helper script in Astro. AgentRef already watches the DOM for late-rendered Stripe links, Buy Buttons, and Pricing Tables. ## Checkout compatibility in Astro | Checkout pattern | Recommendation | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Stripe Payment Links, Buy Buttons, or Pricing Tables embedded in Astro pages | Supported directly. | | Custom server-created Stripe Checkout Session | Supported, but you must pass AgentRef metadata into Stripe on the server. | | External checkout on another root domain | Not automatic. Keep checkout on the same root or build an intentional server-side handoff. | ## Multi-domain and consent notes * Shared subdomains on one root can work well if AgentRef is installed everywhere the journey can begin. * If you use a consent banner script, call `window.AgentRef.setConsent('granted')` after opt-in. * If you run a strict CSP, allow `https://www.agentref.co` for `script-src` and `connect-src`. ## Verify the installation Make sure the layout change is on the deployed site, not just in local preview. Use a real affiliate link and confirm the AgentRef cookies appear. Add `?agentref_debug=1` and inspect the console output. Use the tracking health panel or `verify_tracking`. ## Troubleshooting * The script exists in the layout but cookies never appear: check whether the program website in AgentRef matches the live domain. * Clicks arrive but Stripe conversions do not: your checkout backend still needs the [Stripe guide](/docs/integration/stripe). * The script works locally but not in production: verify the deployed layout actually includes the script and that no CSP blocks it. ## Related docs Metadata bridge for custom Stripe sessions. Full tracking-script reference. Inspect hosted Stripe instrumentation and warnings. # Framer Integration Source: https://agentref.co/docs/integration/framer Install AgentRef on Framer and use Stripe-hosted surfaces without the extra helper scripts other tools often require. Framer is the guide where AgentRef differs most clearly from other affiliate tools: you only need the main tracking script. AgentRef already handles late-rendered Stripe links, Buy Buttons, and Pricing Tables by itself. ## Before you start * Set the program website in AgentRef to the same root domain as your Framer site. * Copy the exact script snippet from `Settings -> Integration`. * Decide whether you use Stripe-hosted surfaces or a custom checkout flow. ## Install the script in Framer 1. Open your Framer project. 2. Go to `Site Settings -> General -> Custom Code`. 3. Paste the AgentRef script into the `End of ` field. ```html theme={null} ``` 4. Publish the site. ## What makes AgentRef different on Framer Other tools often need extra body scripts in Framer to rewrite Stripe links after the page renders. AgentRef already does that internally. That means: * no separate Stripe Payment Link enhancer script * no separate Pricing Table enhancer script * no separate Buy Button enhancer script As long as the AgentRef script is loaded on the page, it will observe the DOM and instrument supported Stripe-hosted surfaces automatically. ## Checkout compatibility on Framer | Flow | Status with AgentRef | | ----------------------------------------------------------------------------- | ------------------------------------------------------------ | | Stripe Payment Links, Buy Buttons, or Pricing Tables directly on Framer pages | Supported directly. | | Custom Stripe checkout handled by your own backend or app | Supported with the [Stripe guide](/docs/integration/stripe). | | External commerce tool that owns checkout without a Stripe metadata bridge | Not automatic. | ## Multi-domain and consent notes * If Framer is just the marketing layer and checkout begins on `app.example.com`, install AgentRef on both surfaces. * Different root domains do not share first-party cookies automatically. * If you use a consent manager, call `window.AgentRef.setConsent('granted')` after opt-in. ## Verify the installation Framer custom code only matters on the published site. Open a live affiliate link and confirm `agentref_cid` and `agentref_pid`. If the page contains Stripe links or elements, use `?agentref_debug=1` and inspect the console or DOM. If you create custom Checkout Sessions elsewhere, follow the [Stripe guide](/docs/integration/stripe). ## Troubleshooting * Payment Links are still plain after publish: confirm the AgentRef script actually loads on the same page as the links. * The Framer page records clicks but conversions never appear: your checkout path is probably outside Framer and still needs the Stripe metadata bridge. * Cookies do not survive from Framer to the app: keep both on one root domain and install AgentRef on both. ## Related docs Use this if Framer hands off into your own Stripe session-creation backend. Inspect hosted Stripe instrumentation and runtime warnings. Full script behavior and DOM API reference. # Installation Guides Source: https://agentref.co/docs/integration/index Choose the right AgentRef installation guide for your framework, site builder, or Stripe checkout setup. AgentRef only needs one tracking script, but the correct installation point depends on your stack. This section gives you a platform-specific guide for each framework and builder linked from the AgentRef dashboard. ## The AgentRef contract in one minute 1. Install the script on every page where affiliate traffic can land or where checkout can start. 2. Set the program website in AgentRef to the same root domain that serves the script. 3. Keep the user journey on the same root domain when possible. `www.example.com` and `app.example.com` work well together. Different roots like `example.com` and `example-store.com` do not share first-party cookies. 4. If you use Stripe Payment Links, Buy Buttons, or Pricing Tables on the same page, the script is usually enough. 5. If you create Stripe Checkout Sessions yourself, read `window.AgentRef.getCheckoutMetadata()` and pass it into Stripe metadata. See the Stripe guide below. 6. If your buy button lives in a Chrome extension, desktop app, mobile app, or external dashboard, route it through a checkout start page on the website where AgentRef is installed before redirecting to Stripe. 7. If a platform uses its own native ecommerce checkout, do not assume automatic attribution. Several builder guides call this out explicitly. Copy the exact script snippet from Settings -> Integration in AgentRef. It already includes your real program ID. ## Framework guides App Router and Pages Router installation for full-site tracking. Install once in your app shell without duplicate script injection. Recommended setup for Vue SPAs and static Vue sites. Add AgentRef through nuxt.config.ts or a shared layout. Install in a reusable Astro layout that covers all landing pages. SvelteKit and plain Svelte installation options. ## Website builder guides Theme, plugin, and child-theme options for site-wide tracking. Theme installation plus a clear note about Shopify native checkout limits. Site-wide custom code setup for published Webflow sites. Framer custom-code setup without extra helper scripts. Add the script through Wix custom code on all published pages. Use code injection for a global AgentRef install. ## Stripe guide Covers hosted Stripe surfaces, custom Checkout Sessions, external app checkout routing, metadata, and verification. ## Verify any installation Use a live test link from AgentRef so the visitor arrives with a real referral parameter like ?via=abc123. In browser DevTools, check that agentref\_cid and agentref\_pid exist after the page loads. Add ?agentref\_debug=1 to the URL and inspect the browser console. You can also use ?agentref\_verify=1 on a live page to trigger a tracking-install verification hit. Use the tracking health panel, the REST endpoint at /api/v1/programs/:id/tracking/status, or the MCP verify\_tracking tool to confirm that the script install was seen and clicks are arriving. ## Related docs Core script behavior, cookies, parameters, and the DOM API. How to gate AgentRef until the visitor grants consent. Inspect runtime state, warnings, and hosted Stripe instrumentation. # Next.js Integration Source: https://agentref.co/docs/integration/nextdotjs Install the AgentRef tracking script in a Next.js app and keep affiliate attribution working across App Router or Pages Router. Next.js is one of the cleanest AgentRef integrations because you can install the tracking script once in a shared layout and cover the whole app. ## Before you start * In AgentRef, set the program website to the same root domain your Next.js app serves. * Copy the exact script snippet from `Settings -> Integration`. * If you split traffic between `www.example.com` and `app.example.com`, install the script on both surfaces if either one can receive affiliate traffic or start checkout. ## Recommended installation Use `next/script` in `app/layout.tsx` so the script loads once for the whole application. ```tsx theme={null} import Script from 'next/script'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ``` ## Checkout compatibility in Nuxt | Checkout pattern | Recommendation | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- | | Hosted Stripe surfaces on Nuxt pages | Supported directly. | | Nuxt server routes or backend create Checkout Sessions | Supported, but you must bridge `window.AgentRef.getCheckoutMetadata()` into Stripe metadata. | | Cross-root-domain checkout | Not automatic. First-party cookies stay on one root domain. | ## Nuxt-specific notes * Install once globally instead of per page. * AgentRef works well with route changes because the script remains loaded after hydration. * If you enable consent mode, call `window.AgentRef.setConsent('granted')` from your banner integration after opt-in. ## Verify the installation 1. Visit the Nuxt app through a live affiliate link. 2. Confirm `agentref_cid` and `agentref_pid` in browser cookies. 3. Add `?agentref_debug=1` and inspect the console. 4. Complete one supported Stripe test checkout and confirm AgentRef records the conversion. ## Troubleshooting * The script only loads on one route: move it into `nuxt.config.ts` or a shared app-level layout. * Clicks appear but no conversion does: your custom session path still needs the [Stripe guide](/docs/integration/stripe). * Consent is required but cookies never appear: your banner integration probably never calls `window.AgentRef.setConsent('granted')`. ## Related docs Required for custom session creation on the server. Gate tracking until the user accepts. Inspect runtime state and warnings. # React Integration Source: https://agentref.co/docs/integration/react Install AgentRef in a React app without duplicate script injection or broken attribution in SPA navigation. For plain React apps, the main goal is simple: load the AgentRef script once at the app shell and keep it available while the SPA navigates. ## Before you start * Set the program website in AgentRef to the same root domain your React app uses. * Copy the exact AgentRef script snippet from `Settings -> Integration`. * If checkout happens on a sibling subdomain, install AgentRef there too. ## Recommended installation For Vite, CRA, and most React SPAs, the cleanest setup is to add the script directly to the shared HTML shell. ```html theme={null} ``` If you do not control the HTML shell, inject the script once from the root component and guard against duplicates. ```tsx theme={null} import { useEffect } from 'react'; export function AgentRefTracking() { useEffect(() => { if (document.querySelector('script[src*="/api/tracking/script.js"]')) { return; } const script = document.createElement('script'); script.defer = true; script.src = 'https://www.agentref.co/api/tracking/script.js?pid=YOUR_PROGRAM_ID'; document.head.appendChild(script); }, []); return null; } ``` If you run React Strict Mode in development, an `index.html` install is usually less noisy than effect-based injection because it avoids duplicate development mounts. ## Checkout compatibility in React | Checkout pattern | Recommendation | | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | Stripe Payment Links, Buy Buttons, or Pricing Tables rendered by the SPA | Supported directly. AgentRef already observes late-rendered Stripe elements. | | React app calls your own backend to create a Stripe Checkout Session | Supported, but you must forward `window.AgentRef.getCheckoutMetadata()` to your server. Use the [Stripe guide](/docs/integration/stripe). | | Checkout jumps to a different root domain | First-party cookies do not cross root domains automatically. | ## SPA-specific notes * AgentRef does not need to be reloaded on every route change. * Hosted Stripe surfaces that render after navigation are still covered because the script watches the DOM. * If you gate cookies behind consent, call `window.AgentRef.setConsent('granted')` after the visitor accepts. ## Verify the installation 1. Land on the app through a real affiliate link. 2. Confirm `agentref_cid` and `agentref_pid` in DevTools. 3. Add `?agentref_debug=1` and inspect the console. 4. Complete one supported checkout flow and confirm AgentRef records the conversion. ## Troubleshooting * The script appears multiple times in the DOM: move the install into `index.html` or add a duplicate guard. * Referral cookies are missing after a route transition: the problem is usually that the script never loaded on first render. * Payment Links work but custom checkout does not: that means your React frontend is fine and your Stripe backend still needs the metadata bridge. ## Related docs Bridge browser attribution into Stripe metadata. Inspect runtime state and hosted Stripe instrumentation. Coordinate AgentRef with your consent banner. # Shopify Integration Source: https://agentref.co/docs/integration/shopify Install AgentRef on Shopify storefront pages and understand the current limits around Shopify's native checkout flow. Shopify is the most important builder guide to read carefully because there are two separate questions: can AgentRef capture affiliate visits on the storefront, and can it automatically attribute Shopify-native orders? Those are not the same thing. ## Before you start * Set the program website in AgentRef to the same root domain as your storefront. * Copy the exact script snippet from `Settings -> Integration`. * Decide whether your sales flow uses native Shopify checkout or a supported Stripe flow outside it. ## Install the script in your storefront theme The most reliable Shopify install is still theme-level and global. 1. In Shopify admin, open `Online Store -> Themes`. 2. On your active theme, click `... -> Edit code`. 3. Open `layout/theme.liquid`. 4. Paste the AgentRef script before the closing `` tag. ```liquid theme={null} ``` 5. Save and publish the theme change. ## What Shopify supports today | Flow | Status with AgentRef | | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | Shopify storefront page with Stripe Payment Links, Buy Buttons, or Pricing Tables embedded directly on the page | Supported directly as long as the AgentRef script loads on that same page. | | Custom app or backend that creates Stripe Checkout Sessions outside Shopify's native checkout | Supported, but you must follow the [Stripe guide](/docs/integration/stripe). | | Native Shopify cart, checkout, or Shop Pay flow | Not an automatic AgentRef attribution surface today. | This is the core product constraint: AgentRef does not automatically read native Shopify order events and turn them into attributed conversions just because the storefront has the tracking script installed. ## What that means in practice * If Shopify is only your marketing layer and the actual paid checkout happens in your own Stripe flow, AgentRef can work well. * If you rely on Shopify's own checkout, use a deliberate server-side reporting strategy or a different supported checkout surface. * Do not promise affiliates automatic Shopify-native attribution unless you have built and tested that bridge intentionally. ## Multi-domain and consent notes * If your storefront is `www.example.com` and your app or billing area is `app.example.com`, install AgentRef on both. * If Shopify uses a different root domain than the page that starts checkout, cookies will not carry across automatically. * If you use a consent banner, call `window.AgentRef.setConsent('granted')` after opt-in. ## Verify the installation Open a real affiliate link to the storefront and confirm `agentref_cid` and `agentref_pid` appear. View page source or DevTools Network on the live storefront. If you use native Shopify checkout, stop here and document that this is not an automatic AgentRef conversion path. If you use Stripe outside Shopify checkout, continue with the [Stripe guide](/docs/integration/stripe). ## Troubleshooting * The script is in the theme editor but not live: the edited theme may not be the active theme. * Storefront clicks show up but conversions do not: this is expected for native Shopify checkout unless you built a separate attribution bridge. * Cookies disappear between storefront and app: install AgentRef on both subdomains and keep everything under one root domain where possible. ## Related docs Use this if your paid flow leaves native Shopify checkout and creates Stripe sessions directly. Fallback when the platform-controlled checkout path cannot pass AgentRef through automatically. Understand what the script does and what it does not do. # Squarespace Integration Source: https://agentref.co/docs/integration/squarespace Install AgentRef with Squarespace code injection and understand where Squarespace-native checkout does or does not map to AgentRef automatically. Squarespace is a good AgentRef fit for marketing pages and hosted Stripe surfaces. As with other website builders, the important distinction is whether Squarespace only renders the page or also owns the checkout. ## Before you start * Set the program website in AgentRef to the same root domain as your Squarespace site. * Copy the exact script snippet from `Settings -> Integration`. * If checkout begins on another subdomain, install AgentRef there too. ## Install the script with code injection 1. Open your Squarespace site dashboard. 2. Go to `Settings -> Developer Tools -> Code Injection`. 3. Paste the AgentRef script into the `Header` field. ```html theme={null} ``` 4. Save the change. Squarespace code injection is typically available on Business plans and higher. ## Checkout compatibility on Squarespace | Flow | Status with AgentRef | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------ | | Stripe Payment Links, Buy Buttons, or Pricing Tables embedded on Squarespace pages | Supported directly. | | Custom Stripe checkout outside Squarespace Commerce | Supported with the [Stripe guide](/docs/integration/stripe). | | Squarespace-native commerce checkout | Not an automatic AgentRef attribution surface today. | ## Multi-domain and consent notes * Shared subdomains under one root can work well if AgentRef is installed on every surface that starts the journey. * Different root domains do not share first-party cookies automatically. * If you use a consent banner, call `window.AgentRef.setConsent('granted')` after opt-in. ## Verify the installation Use a real affiliate link on the published Squarespace domain. Check for `agentref_cid` and `agentref_pid` in DevTools. Add `?agentref_debug=1` and inspect the console. If you use anything beyond hosted Stripe surfaces, follow the [Stripe guide](/docs/integration/stripe). ## Troubleshooting * The script is saved but cookies never appear: confirm the published domain matches the website configured in AgentRef. * Clicks exist but conversions do not: Squarespace Commerce native checkout is not an automatic AgentRef conversion path. * The flow crosses into another domain: first-party cookies will not move across root domains without a deliberate handoff. ## Related docs Required for custom Squarespace-to-Stripe flows. Understand the full click-to-conversion pipeline. Coordinate AgentRef with your privacy banner. # Stripe Checkout Integration Source: https://agentref.co/docs/integration/stripe Connect AgentRef to Stripe Payment Links, Buy Buttons, Pricing Tables, or custom Checkout Sessions. This guide is the exact Stripe contract behind AgentRef's website integration. Use it whenever your site already has the AgentRef tracking script installed and you need to make sure conversions are attributed correctly. ## Routing decision first Choose the route before changing code: | Route | Use when | What to do | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Route A — Hosted Stripe on the AgentRef website | A Stripe Payment Link, Buy Button, or Pricing Table is rendered on the website where AgentRef is installed. | Keep the flow intact and verify the AgentRef script is on that page. | | Route B — Website custom checkout | The buy button is rendered on the website where AgentRef is installed, and that website calls your backend to create Stripe Checkout. | Read `window.AgentRef.getCheckoutMetadata()` on the website and pass it into Stripe metadata. | | Route C — External app checkout | The buy button is rendered in a Chrome extension, desktop app, mobile app, external dashboard, or another surface where AgentRef is not installed. | Stop before implementation, explain the required checkout-route code change, ask for approval, then route checkout through a checkout start page on the AgentRef website before redirecting to Stripe. | If Route C applies, it overrides Route B even when your backend creates the Stripe Checkout Session. Do not treat a metadata-only patch inside the external app as complete. ## First, identify your Stripe checkout type | Checkout type | What AgentRef needs | | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Stripe Payment Links | Script on the page with the link. No backend bridge required. | | Stripe Buy Buttons | Script on the page with the button. No backend bridge required. | | Stripe Pricing Tables | Script on the page with the pricing table. No backend bridge required. | | Server-created Stripe Checkout Session | Script plus a metadata bridge from browser to server to Stripe. | | Direct PaymentIntents / custom Stripe flow | Script plus a metadata bridge into your payment intent or checkout backend. | | External app, Chrome extension, desktop app, or mobile app checkout | Route the buy button through a checkout start page on the website where the AgentRef script is installed, then redirect to Stripe. | AgentRef already instruments hosted Stripe surfaces by itself. If the page contains a real buy.stripe.com link, \, or \, you do not need an extra helper script. If a flow is both server-created and started from a Chrome extension, desktop app, mobile app, or external dashboard, treat it as an external app checkout. A metadata-only patch inside the external app is not enough unless that app can actually read the AgentRef cookies from the website where the script is installed. ## Hosted Stripe surfaces If you use Stripe Payment Links, Buy Buttons, or Pricing Tables, install the AgentRef script on the same page as the Stripe element and keep the rest of your flow intact. ### What AgentRef does automatically * Appends `client_reference_id=` to `buy.stripe.com` links * Sets `client-reference-id` on `stripe-buy-button` * Sets `client-reference-id` on `stripe-pricing-table` * Watches for late-rendered Stripe elements with a `MutationObserver` That means hosted Stripe surfaces work well for static sites, SPAs, and builders as long as the AgentRef script loads on the page where the visitor clicks. ## Custom Checkout Sessions If your CTA goes to your own route, API, server action, or backend first, the script alone is not enough. You need one small bridge: read the AgentRef checkout metadata in the browser and forward it into Stripe when your server creates the session. ### Step 1: Read AgentRef metadata in the browser ```js theme={null} window.AgentRef.ready(async function () { const agentrefMeta = window.AgentRef.getCheckoutMetadata(); const response = await fetch('/api/create-checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId: 'price_123', agentrefMeta, }), }); const { url } = await response.json(); window.location.href = url; }); ``` ### Step 2: Pass the metadata into Stripe on the server ```ts Node.js theme={null} const session = await stripe.checkout.sessions.create({ mode: 'subscription', line_items: [{ price: priceId, quantity: 1 }], success_url: 'https://example.com/success', cancel_url: 'https://example.com/pricing', metadata: { ...(agentrefMeta || {}), }, subscription_data: { metadata: { ...(agentrefMeta || {}), }, }, }); ``` ```ts One-time payment theme={null} const session = await stripe.checkout.sessions.create({ mode: 'payment', line_items: [{ price: priceId, quantity: 1 }], success_url: 'https://example.com/success', cancel_url: 'https://example.com/pricing', metadata: { ...(agentrefMeta || {}), }, payment_intent_data: { metadata: { ...(agentrefMeta || {}), }, }, }); ``` ## External apps and Chrome extensions If the buy button does not live on the website where the AgentRef script is installed, do not send the user directly from that external surface to Stripe. This includes Chrome extensions, desktop apps, mobile apps, external dashboards, and any app surface running on a different origin. Do not treat a metadata-only patch inside the external app as complete. The AgentRef cookie usually lives on your website, not inside `chrome-extension://...` or another external origin. Instead, route the button to a checkout start page on your website: ```txt theme={null} Chrome extension or external app -> https://your-domain.com/checkout/start?plan=pro -> your website reads AgentRef attribution -> your backend creates the Stripe Checkout Session with AgentRef metadata -> redirect to Stripe Checkout ``` The checkout start page should run on the same root domain as your AgentRef script. In the simplest implementation, that page loads your normal app shell, calls `window.AgentRef.getCheckoutMetadata()`, sends the metadata to your existing checkout backend, and then redirects to the Stripe Checkout URL. If an AI coding agent detects this external-app pattern, instruct it to stop before implementation, explain that checkout must route through the website first, and ask for approval before editing code. This is a code-change approval gate for the checkout-route rewrite, not a deployment approval gate. It avoids silent checkout-flow rewrites and incomplete metadata-only patches. ## Exact metadata keys AgentRef expects the metadata object to pass through unchanged: ```json theme={null} { "agentref_cid": "clk_abc123", "agentref_pid": "550e8400-e29b-41d4-a716-446655440000", "agentref_source": "script_direct" } ``` Do not rename these keys, flatten them differently, or trim them down to only one field. AgentRef's Stripe webhook reads this contract exactly. ## What not to do * Do not add a second attribution or reporting pipeline for the same Stripe payment. * Do not overwrite your own `client_reference_id` on custom Checkout Sessions unless that field is already reserved for AgentRef. * Do not send external app or Chrome extension buy buttons directly to Stripe when the AgentRef script only runs on your website. * Do not treat an external app request-schema or Stripe-metadata patch as complete if the flow still starts checkout outside the AgentRef website. * Do not let an AI coding agent silently reroute checkout without your approval. * Do not assume Shopify, Webflow Ecommerce, WooCommerce, Wix Stores, or other platform-native checkouts will magically pass this metadata into Stripe. If the platform owns checkout and does not expose Stripe session creation, treat that as a separate integration problem. ## Verify the Stripe path Open a real affiliate link and make sure agentref\_cid and agentref\_pid are present before checkout starts. Complete a test purchase through the exact flow you use in production. In Stripe, confirm that the session metadata includes agentref\_cid, agentref\_pid, and agentref\_source, or that the hosted surface received a client\_reference\_id. In AgentRef, confirm the conversion appears under Conversions and the tracking diagnostics no longer show a Stripe-event blocker. ## Troubleshooting | Problem | What to check | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Hosted Stripe link was clicked but no conversion appeared | Make sure the AgentRef script was loaded on the same page as the link or Stripe element. | | Custom session created but conversion missing | Check that your backend wrote the AgentRef keys into `metadata` and `subscription_data.metadata` or `payment_intent_data.metadata`. | | Chrome extension or external app checkout is missing attribution | Route the buy button through a checkout start page on the website where the AgentRef script is installed, then create Stripe Checkout from there. | | Pricing table or buy button renders late | AgentRef already watches the DOM for late Stripe elements. Usually this means the script was not installed at all or loaded on the wrong page. | | Tracking health still shows a Stripe blocker | You may have clicks but no completed Stripe event yet. Finish one supported checkout flow end-to-end. | ## Related docs Core script behavior and `window.AgentRef`. Full click-to-commission pipeline. Inspect hosted Stripe instrumentation and runtime warnings. # Svelte and SvelteKit Integration Source: https://agentref.co/docs/integration/svelte Install AgentRef in SvelteKit or plain Svelte and keep attribution stable across navigation. Svelte and SvelteKit both work well with AgentRef as long as the script is installed in the global shell instead of a page-specific component. ## Before you start * Set the program website in AgentRef to the same root domain your app serves. * Copy the exact script snippet from `Settings -> Integration`. * If your app and checkout start on different subdomains, install AgentRef on both. ## Recommended installation Install the script in `src/app.html` so it is available across all routes. ```html theme={null} %sveltekit.head%
%sveltekit.body%
```
For plain Svelte apps, install the script in the shared HTML shell. ```html theme={null} ```
## Checkout compatibility in Svelte | Checkout pattern | Recommendation | | ----------------------------------------------- | ------------------------------------------------------------ | | Hosted Stripe surfaces on Svelte pages | Supported directly. | | Custom Checkout Sessions created by your server | Supported with the [Stripe guide](/docs/integration/stripe). | | Checkout on a different root domain | Not automatic because AgentRef uses first-party cookies. | ## Svelte-specific notes * Do not reinstall AgentRef in `onMount()` for every route. One shell-level install is enough. * Hosted Stripe elements rendered after hydration are still observed automatically. * If you gate cookies behind consent, call `window.AgentRef.setConsent('granted')` after opt-in. ## Verify the installation 1. Open the live app through a real affiliate link. 2. Confirm `agentref_cid` and `agentref_pid` in browser cookies. 3. Add `?agentref_debug=1` and inspect the console. 4. Complete one supported checkout flow and verify the conversion in AgentRef. ## Troubleshooting * `window.AgentRef` is undefined: the script was not installed in the global shell. * Clicks appear but conversions do not: your custom checkout path still needs the [Stripe metadata bridge](/docs/integration/stripe). * Cookies fail across domains: move the journey onto one root or handle the handoff server-side. ## Related docs Metadata bridge for custom Stripe sessions. Coordinate AgentRef with your consent banner. Runtime inspection and troubleshooting. # Vue.js Integration Source: https://agentref.co/docs/integration/vuedotjs Install AgentRef in a Vue app and keep attribution stable across client-side navigation. Vue works best with AgentRef when the script is installed once in the app shell instead of being recreated on every page. ## Before you start * Set the program website in AgentRef to the same root domain your Vue app serves. * Copy the exact script snippet from `Settings -> Integration`. * If your marketing site and checkout app live on different subdomains, install on both when both can start the conversion path. ## Recommended installation For Vue apps built with Vite, install the script in the shared HTML shell. ```html theme={null} ``` If you need a runtime install, append the script before mounting the app. ```ts theme={null} import { createApp } from 'vue'; import App from './App.vue'; if (!document.querySelector('script[src*="/api/tracking/script.js"]')) { const script = document.createElement('script'); script.defer = true; script.src = 'https://www.agentref.co/api/tracking/script.js?pid=YOUR_PROGRAM_ID'; document.head.appendChild(script); } createApp(App).mount('#app'); ``` ## Checkout compatibility in Vue | Checkout pattern | Recommendation | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | Stripe Payment Links, Buy Buttons, or Pricing Tables | Supported directly. Keep the script on the page that renders them. | | Vue frontend calls an API that creates Stripe Checkout Sessions | Supported, but you must pass `window.AgentRef.getCheckoutMetadata()` to that API first. | | Separate root-domain checkout | Not automatic. Cookies are first-party and stay on one root. | ## Notes for Vue SPAs * AgentRef does not need to be re-mounted on route changes. * Late-rendered Stripe elements are still instrumented automatically. * If you use a cookie banner component, call `window.AgentRef.setConsent('granted')` after the visitor accepts. ## Verify the installation Open a live affiliate link that lands on your Vue app. Confirm `agentref_cid` and `agentref_pid`, then add `?agentref_debug=1`. Use the AgentRef dashboard, tracking status endpoint, or MCP `verify_tracking` tool. ## Troubleshooting * `window.AgentRef` never appears: install the script earlier, ideally in `index.html`. * Conversions are missing while clicks arrive: your Stripe session creation path still needs the [Stripe guide](/docs/integration/stripe). * Cookies disappear between domains: use one shared root domain or install deliberately on every participating subdomain. ## Related docs Metadata handoff for custom checkout sessions. Cookie, parameter, and DOM API reference. Runtime inspection and warnings. # Webflow Integration Source: https://agentref.co/docs/integration/webflow Install AgentRef on a Webflow site and understand when Webflow pages are a good fit for AgentRef's supported checkout contract. Webflow is a strong fit for AgentRef when it is used for a marketing site, landing pages, or pages that host Stripe links and embeds. ## Before you start * Set the program website in AgentRef to the same root domain your Webflow site uses. * Copy the exact script snippet from `Settings -> Integration`. * If your app or checkout starts on another subdomain, install AgentRef there too. ## Install the script site-wide 1. Open your Webflow project. 2. Go to `Site Settings -> Custom Code`. 3. Paste the AgentRef script into the `Head Code` field. ```html theme={null} ``` 4. Save the change. 5. Publish the site. Webflow custom code only runs on the published site, not in the Designer preview. ## Checkout compatibility on Webflow | Flow | Status with AgentRef | | ------------------------------------------------------------------------------ | ------------------------------------------------------------ | | Stripe Payment Links, Buy Buttons, or Pricing Tables embedded on Webflow pages | Supported directly. | | Custom Stripe checkout you own outside Webflow Ecommerce | Supported with the [Stripe guide](/docs/integration/stripe). | | Webflow Ecommerce native checkout | Not an automatic AgentRef attribution surface today. | ## Multi-domain and consent notes * Webflow marketing site plus app subdomain is fine if both live under one root and both install AgentRef where needed. * Different root domains do not share first-party cookies automatically. * If you gate cookies behind consent, call `window.AgentRef.setConsent('granted')` after opt-in. ## Verify the installation 1. Publish the site. 2. Open a real affiliate link to the live Webflow domain. 3. Confirm `agentref_cid` and `agentref_pid` exist in browser cookies. 4. Add `?agentref_debug=1` and inspect the console. 5. If you use custom Stripe checkout, validate the [Stripe guide](/docs/integration/stripe) end-to-end. ## Troubleshooting * The script works in code settings but not live: republish the site after saving. * The script exists but clicks are not verified: confirm the program website in AgentRef matches the Webflow domain. * Conversions are missing while clicks arrive: you are either using Webflow Ecommerce native checkout or your custom Stripe path still needs the metadata bridge. ## Related docs Required for custom Webflow-to-Stripe checkout paths. Inspect hosted Stripe instrumentation and runtime warnings. Coordinate AgentRef with your consent manager. # Wix Integration Source: https://agentref.co/docs/integration/wix Install AgentRef on a Wix site and understand the limits of Wix-native ecommerce checkout with AgentRef. Wix works well for AgentRef as a marketing site or a page that hosts supported Stripe elements. Like several builders, it becomes a different conversation once Wix owns the actual checkout path. ## Before you start * Set the program website in AgentRef to the same root domain your Wix site uses. * Copy the exact script snippet from `Settings -> Integration`. * If your app or checkout starts on another subdomain, install AgentRef there too. ## Install the script through Wix custom code 1. Open the Wix Editor for your site. 2. Go to `Settings -> Custom Code`. 3. Click `Add Custom Code`. 4. Choose `All Pages`. 5. Choose placement in `Head`. 6. Paste the AgentRef script: ```html theme={null} ``` 7. Apply the change and publish the site. Wix custom code runs on the published site, not in editor preview. ## Checkout compatibility on Wix | Flow | Status with AgentRef | | ----------------------------------------------------------------- | ------------------------------------------------------------ | | Stripe Payment Links, Buy Buttons, or Pricing Tables on Wix pages | Supported directly. | | Custom Stripe checkout outside Wix Stores | Supported with the [Stripe guide](/docs/integration/stripe). | | Wix Stores or another Wix-native ecommerce checkout | Not an automatic AgentRef attribution surface today. | ## Multi-domain and consent notes * Shared subdomains under one root work best when AgentRef is installed on both sides of the journey. * Different root domains cannot share first-party cookies automatically. * If you use a cookie banner, call `window.AgentRef.setConsent('granted')` after opt-in. ## Verify the installation 1. Publish the site. 2. Visit the live Wix domain through a real affiliate link. 3. Confirm `agentref_cid` and `agentref_pid` in browser cookies. 4. Add `?agentref_debug=1` and inspect the console. 5. If you use custom Stripe checkout, verify the [Stripe guide](/docs/integration/stripe) path too. ## Troubleshooting * The code is saved in Wix but not live: publish the site again after the change. * Clicks appear but no conversion does: you are likely in a Wix-native checkout path or missing the Stripe metadata bridge. * Cookies disappear between marketing site and app: install AgentRef on both subdomains and keep them on one root domain. ## Related docs Required for custom Wix-to-Stripe flows. Fallback when the platform-owned checkout cannot pass AgentRef automatically. Coordinate AgentRef with your privacy banner. # WordPress Integration Source: https://agentref.co/docs/integration/wordpress Install AgentRef on a WordPress site and understand when WordPress checkout flows do or do not map cleanly to AgentRef. WordPress gives you multiple installation options, but the safest AgentRef setup is still the same: load the script site-wide and keep checkout on a supported Stripe path. ## Before you start * Set the program website in AgentRef to the same root domain your WordPress site uses. * Copy the exact script snippet from `Settings -> Integration`. * If WordPress is only your marketing site and checkout happens on `app.example.com`, install AgentRef on both surfaces. ## Recommended installation If your site already uses a site-wide header-code plugin or theme option, paste the AgentRef script into the global head field. ```html theme={null} ``` If you prefer code, add the script through `wp_head` in your child theme instead of editing the parent theme directly. ```php theme={null} add_action('wp_head', function () { ?> If you use caching, minification, or CDN plugins, clear those caches after installing the script. ## Checkout compatibility on WordPress | Checkout pattern | Recommendation | | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Stripe Payment Links, Buy Buttons, or Pricing Tables embedded on WordPress pages | Supported directly. | | Custom Stripe Checkout Session created by your own WordPress plugin or backend code | Supported, but you must follow the [Stripe guide](/docs/integration/stripe). | | WooCommerce or another native WordPress ecommerce checkout | Not an automatic AgentRef checkout surface today unless you explicitly bridge attribution into Stripe metadata or report conversions server-side. | This is the key WordPress caveat: installing the script on a WooCommerce storefront captures referral visits, but it does not by itself guarantee automatic order attribution through WooCommerce's native checkout path. ## Multi-domain and consent notes * Shared subdomains under one root can share AgentRef cookies if the script is installed where the journey begins. * Different root domains cannot share first-party cookies automatically. * If you use a consent manager, call `window.AgentRef.setConsent('granted')` after the visitor opts in. ## Verify the installation Land on your published WordPress site with a real referral link. Check for `agentref_cid` and `agentref_pid` in browser cookies. Add `?agentref_debug=1` or `?agentref_verify=1` to a live page and inspect the result. If you use anything beyond hosted Stripe surfaces, confirm your checkout integration matches the [Stripe guide](/docs/integration/stripe). ## Troubleshooting * The script is visible in WordPress admin but not on the live site: a cache or optimization layer is still serving the old template. * Clicks arrive but sales do not: this usually means WooCommerce or another plugin owns checkout and does not pass AgentRef metadata into Stripe. * Cookies exist on the marketing site but not in the app: install AgentRef on both subdomains. ## Related docs Required for custom WordPress-to-Stripe checkout bridges. Use this when your commerce stack cannot pass through AgentRef automatically. Coordinate AgentRef with your cookie banner. # Connect Stripe Source: https://agentref.co/docs/merchant-guide/connect-stripe Link your Stripe account to AgentRef so affiliate conversions are tracked automatically from payment events. AgentRef uses Stripe Connect (OAuth) to listen for payment events on your account. When a customer pays, AgentRef matches the payment to the affiliate who referred them and creates a conversion automatically. No payment processing access is required. AgentRef only reads payment events. ## What happens during connection When you click **Connect Stripe** in the dashboard (or call the API), the flow works like this: ```mermaid theme={null} sequenceDiagram participant M as Merchant participant A as AgentRef participant S as Stripe M->>A: Click "Connect Stripe" A->>M: Redirect to Stripe OAuth page M->>S: Authorize AgentRef S->>A: OAuth callback with auth code A->>S: Exchange code for access token A->>A: Store connection, register webhooks A->>M: Redirect back to dashboard (connected) ``` From the dashboard, go to **Program Settings > Stripe** and click **Connect Stripe**. You'll be redirected to Stripe's authorization page. Alternatively, use the API to get an OAuth URL: ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/v1/programs/PROGRAM_ID/connect-stripe \ -H "Authorization: Bearer ak_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"method": "oauth_url"}' ``` ```javascript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_YOUR_KEY' }); const result = await client.programs.connectStripe('PROGRAM_ID', { method: 'oauth_url', }); console.log(result.authUrl); // Redirect the merchant to this URL ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef(api_key="ak_live_YOUR_KEY") result = client.programs.connect_stripe("PROGRAM_ID", method="oauth_url") print(result.auth_url) # Redirect the merchant to this URL ``` The response includes an `authUrl` that you open in the browser to start the Stripe OAuth flow. On the Stripe authorization page, review the permissions AgentRef is requesting and click **Connect**. If you have multiple Stripe accounts, select the one you want to use for this program. After authorization, AgentRef automatically registers webhooks on your Stripe account. You do not need to configure anything manually in Stripe's dashboard. ## Permissions AgentRef receives AgentRef requests **read-only** access to payment events. Here is exactly what it can and cannot do: | Can do | Cannot do | | -------------------------------------- | ----------------------------------- | | Listen for payment events via webhooks | Process payments on your behalf | | Read checkout session metadata | Access your bank account or balance | | Read invoice and subscription data | Create charges or transfers | | Read refund events | Modify your Stripe settings | AgentRef uses Stripe Connect in **read-only mode**. It never touches your funds or payment processing. You can revoke access at any time from your Stripe dashboard under **Settings > Connected accounts**. ## Webhook events AgentRef listens to Once connected, AgentRef automatically subscribes to these Stripe webhook events: | Event | Purpose | | ------------------------------- | --------------------------------------------------------------- | | `checkout.session.completed` | Captures one-time purchases and new subscriptions from Checkout | | `invoice.paid` | Tracks recurring subscription payments | | `charge.succeeded` | Captures direct charges (Payment Intents, legacy charges) | | `charge.refunded` | Reverses commissions when a refund is issued | | `customer.subscription.deleted` | Stops recurring commissions when a subscription is canceled | These webhooks are managed by AgentRef. You do not need to create or maintain them in Stripe's webhook settings. If you already have a webhook endpoint in Stripe pointing to AgentRef from a previous connection, the reconnect flow will reuse it. AgentRef does not create duplicate endpoints. ## Disconnect Stripe If you need to disconnect Stripe from a program: ```bash cURL theme={null} curl -X DELETE https://www.agentref.co/api/v1/programs/PROGRAM_ID/connect-stripe \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ```javascript Node.js theme={null} const result = await client.programs.disconnectStripe('PROGRAM_ID'); console.log(result.success); // true ``` ```python Python theme={null} result = client.programs.disconnect_stripe("PROGRAM_ID") print(result.success) # True ``` Disconnecting removes the webhook registration and stops conversion tracking for that program. Existing conversions and commissions are not affected. ## Troubleshooting | Problem | Solution | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | OAuth flow fails or times out | Check that third-party cookies are enabled in your browser. Some ad blockers interfere with the OAuth redirect. | | Webhooks not firing | Verify the connection is active in **Program Settings > Stripe**. If needed, disconnect and reconnect. | | Conversions not appearing | Check that your tracking script passes `agentref_cid` metadata to Stripe. See [Install Tracking](/docs/merchant-guide/install-tracking). | | Multiple Stripe accounts | Each program connects to one Stripe account. Use separate programs for separate Stripe accounts. | ## What's next Configure your commission rates, cookie duration, and program settings. Add the tracking script to your website to capture affiliate clicks. # Conversions Source: https://agentref.co/docs/merchant-guide/conversions Understand the conversion lifecycle, how Stripe events create conversions, and how to query and aggregate your conversion data. A conversion is created every time an affiliate-referred customer completes a purchase. It records the sale amount, the commission owed, and the current status of that commission. Conversions are the backbone of your affiliate program – everything from payouts to fraud detection is tied to them. ## Conversion lifecycle ``` pending → approved → paid ↓ ↓ rejected partially_refunded → refunded ``` | Status | What it means | | -------------------- | ------------------------------------------------------------------------------ | | `pending` | Conversion created from a Stripe event. Waiting for review or approval window. | | `approved` | Commission confirmed. Will be included in the next payout. | | `partially_refunded` | Part of the Stripe charge was refunded. Commission is reduced proportionally. | | `refunded` | The underlying Stripe charge was fully refunded. Commission is voided. | | `rejected` | Manually rejected or flagged as fraudulent. Commission is voided. | Most programs approve conversions automatically. If your program has fraud detection enabled, suspicious conversions may stay in `pending` until you resolve the associated flag. ## How conversions are created AgentRef listens to your connected Stripe account for `checkout.session.completed` and `invoice.payment_succeeded` events. When a purchase is traced back to an affiliate click (via cookie or referral code), a conversion is created automatically – no server-side code needed beyond the tracking snippet. The conversion captures: * `amount` – the charged amount in cents * `currency` – from the Stripe charge * `customerEmail` – from the Stripe customer * `commission` – calculated from your program's `commissionPercent` * `commissionType` – `one_time` or `recurring` / `recurring_limited` ## Viewing conversions in the dashboard Go to **Programs > \[Your Program] > Conversions**. You can filter by status, date range, or affiliate. Click any row to see the full conversion detail including the originating click and Stripe charge ID. ## API reference ### List conversions Scope: `conversions:read` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/conversions?status=pending&limit=25" \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ```javascript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_YOUR_KEY' }); const { data, meta } = await client.conversions.list({ status: 'pending', limit: 25, }); for (const conversion of data) { console.log(conversion.id, conversion.amount, conversion.status); } ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef(api_key='ak_live_YOUR_KEY') result = client.conversions.list(status='pending', limit=25) for conversion in result.data: print(conversion.id, conversion.sale_amount, conversion.status) ``` **Query parameters** | Parameter | Type | Description | | -------------------- | ------------ | --------------------------------------------------------------------------- | | `programId` | UUID | Filter to a specific program | | `affiliateId` | UUID | Filter to a specific affiliate | | `status` | string | `pending` \| `approved` \| `partially_refunded` \| `rejected` \| `refunded` | | `startDate` / `from` | ISO datetime | Start of date range | | `endDate` / `to` | ISO datetime | End of date range | | `limit` | number | Max results (1–100) | | `page` / `offset` | number | Pagination | ### Get conversion stats Returns aggregate metrics for a time period. Scope: `stats:read` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/conversions/stats?period=30d&programId=PROGRAM_ID" \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ```javascript Node.js theme={null} const stats = await client.conversions.stats({ period: '30d', programId: 'PROGRAM_ID', }); console.log(stats.total); console.log(stats.totalRevenue); // in cents console.log(stats.totalCommissions); ``` ```python Python theme={null} stats = client.conversions.stats(period='30d', program_id='PROGRAM_ID') print(stats.total) print(stats.total_revenue) # in cents print(stats.total_commissions) ``` **Period options:** `7d`, `30d`, `90d`, `all` ### Get recent conversions Returns the most recent conversions (up to 20). Useful for dashboard widgets and webhooks verification. Scope: `conversions:read` ```bash cURL theme={null} curl "https://www.agentref.co/api/v1/conversions/recent?limit=5" \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ```javascript Node.js theme={null} const recent = await client.conversions.recent({ limit: 5 }); ``` ```python Python theme={null} recent = client.conversions.recent(limit=5) ``` ## What's next Learn how approved conversions become payouts to your affiliates. See how AgentRef flags suspicious conversions before they're approved. # Create Program Source: https://agentref.co/docs/merchant-guide/create-program Set up your affiliate program with commission rates, cookie duration, payout rules, and approval settings. A program is the core unit in AgentRef. It defines how affiliates earn commissions, how long attribution cookies last, and when payouts happen. You can run multiple programs under one merchant account. ## Create a program After connecting Stripe, the onboarding flow walks you through creating your first program. You can also create additional programs from **Programs > New Program** at any time. ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/v1/programs \ -H "Authorization: Bearer ak_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My SaaS Affiliate Program", "commissionType": "recurring", "commissionPercent": 25, "cookieDuration": 30, "payoutThreshold": 5000, "payoutFrequency": "monthly", "autoApproveAffiliates": true, "currency": "usd" }' ``` ```javascript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_YOUR_KEY' }); const program = await client.programs.create({ name: 'My SaaS Affiliate Program', commissionType: 'recurring', commissionPercent: 25, cookieDuration: 30, payoutThreshold: 5000, payoutFrequency: 'monthly', autoApproveAffiliates: true, currency: 'usd', }); console.log(program.id); ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef(api_key="ak_live_YOUR_KEY") program = client.programs.create( name="My SaaS Affiliate Program", commission_type="recurring", commission_percent=25, cookie_duration=30, payout_threshold=5000, payout_frequency="monthly", auto_approve_affiliates=True, currency="usd", ) print(program.id) ``` ## Program settings ### Required settings | Setting | Type | Description | | ------------------- | ------ | ---------------------------------------------------------------------------------------------- | | `name` | string | Display name for your program. Shown to affiliates and in the marketplace. Max 100 characters. | | `commissionType` | string | How commissions are calculated. See commission types below. | | `commissionPercent` | number | Percentage of each sale the affiliate earns. Range: 1–100. | ### Optional settings | Setting | Default | Description | | ----------------------- | --------- | ------------------------------------------------------------------------------------------------------- | | `description` | `null` | Program description (max 500 characters). Shown in marketplace listings. | | `landingPageUrl` | `null` | Default URL where affiliate links redirect visitors. | | `cookieDuration` | `30` | Days the attribution cookie persists. Range: 1–365. | | `payoutThreshold` | `5000` | Minimum commission balance (in cents) before an affiliate is eligible for payout. Range: 1,000–100,000. | | `payoutFrequency` | `monthly` | How often payouts are processed: `weekly`, `monthly`, or `quarterly`. | | `autoApproveAffiliates` | `false` | Whether new affiliates are approved automatically or require manual review. | | `commissionLimitMonths` | `null` | For `recurring_limited` type only. Number of months commissions are paid. Range: 1–36. | | `portalSlug` | `null` | Custom slug for your affiliate portal URL. Lowercase alphanumeric and hyphens only. | | `currency` | `usd` | Currency code for commissions and payouts. | The `payoutThreshold` is specified in **cents**. A value of `5000` means \$50.00. ## Commission types AgentRef supports three commission models: ### One-time (`one_time`) The affiliate earns a commission on the first payment only. If a customer upgrades, renews, or makes additional purchases, no further commissions are generated. Best for: One-time purchases, lifetime deals, products without subscriptions. ### Recurring (`recurring`) The affiliate earns a commission on every payment the referred customer makes, for the lifetime of the subscription. If the customer pays monthly, the affiliate earns every month. Best for: SaaS with subscriptions. This is the most popular model because it incentivizes affiliates to refer customers who stick around. ### Recurring with limit (`recurring_limited`) Same as recurring, but commissions stop after a configured number of months (`commissionLimitMonths`). For example, 25% for the first 12 months. Best for: SaaS that wants to cap lifetime affiliate payouts while still rewarding ongoing referrals during the customer's early months. | Type | First payment | Subsequent payments | Cap | | ------------------- | ----------------- | ------------------- | -------------------- | | `one_time` | Commission earned | No commission | N/A | | `recurring` | Commission earned | Commission earned | None | | `recurring_limited` | Commission earned | Commission earned | Stops after N months | ## Program status lifecycle Every program has a `status` that controls whether it is actively tracking conversions: ```mermaid theme={null} graph LR A[active] -->|Pause| B[paused] B -->|Resume| A A -->|Archive| C[archived] B -->|Archive| C ``` | Status | Behavior | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `active` | Tracking is live. New clicks, conversions, and commissions are processed normally. | | `paused` | Tracking stops. Existing data is preserved but no new conversions are created. Affiliate links still resolve but clicks are not recorded. | | `archived` | Program is soft-deleted. It no longer appears in listings or API responses (unless explicitly filtered). Existing conversions and payouts are preserved for historical records. | Update a program's status via the API: ```bash cURL theme={null} curl -X PATCH https://www.agentref.co/api/v1/programs/PROGRAM_ID \ -H "Authorization: Bearer ak_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "paused"}' ``` ```javascript Node.js theme={null} await client.programs.update('PROGRAM_ID', { status: 'paused' }); ``` ```python Python theme={null} client.programs.update("PROGRAM_ID", status="paused") ``` ## Program readiness AgentRef tracks whether your program is fully configured and ready to process conversions. The `readiness` field on a program can be: | Readiness | Meaning | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `setup` | Initial state. Stripe is not connected and tracking is not installed. | | `partial` | Some steps are complete but the program is not fully operational. For example, Stripe is connected but the tracking script has not been verified. | | `ready` | Stripe is connected, tracking script is verified, and the program can process conversions end-to-end. | Check readiness via the API by fetching the program detail: ```bash cURL theme={null} curl https://www.agentref.co/api/v1/programs/PROGRAM_ID \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ```javascript Node.js theme={null} const program = await client.programs.get('PROGRAM_ID'); console.log(program.readiness); // "setup" | "partial" | "ready" ``` ```python Python theme={null} program = client.programs.get("PROGRAM_ID") print(program.readiness) # "setup" | "partial" | "ready" ``` ## Recommended starting settings If you are unsure where to start, these are industry-standard defaults for SaaS affiliate programs: | Setting | Recommendation | Why | | -------------------- | -------------- | ---------------------------------------------------------------------------- | | **Commission type** | `recurring` | Incentivizes affiliates to refer customers who stick around. | | **Commission rate** | 20–30% | Competitive enough to attract affiliates. Industry standard for SaaS. | | **Cookie duration** | 30 days | Balances fair attribution with a reasonable conversion window. | | **Payout threshold** | \$50 | Low enough to keep affiliates motivated, high enough to avoid micro-payouts. | | **Payout frequency** | Monthly | Standard cadence. Weekly works for high-volume programs. | | **Auto-approve** | Yes | Reduces friction. You can always block bad actors later. | You can change all of these settings later. Start simple and adjust based on real performance data. ## What's next Link your Stripe account if you haven't already. Add the tracking script to your website to start capturing clicks. # Fraud Detection Source: https://agentref.co/docs/merchant-guide/fraud-detection How AgentRef detects and flags suspicious affiliate activity automatically. AgentRef automatically monitors affiliate activity and flags suspicious patterns. You decide what to do with each flag. ## How It Works AgentRef analyzes clicks, conversions, and affiliate behavior in real-time. When something looks suspicious, a **fraud flag** is created for your review. ### Flag Types | Flag Type | What It Detects | | -------------------------- | ------------------------------------------------------ | | Suspicious click patterns | Unusual click volume or timing from a single affiliate | | Self-referrals | Affiliate referring their own purchases | | Geographic anomalies | Clicks from unexpected regions | | Conversion velocity | Too many conversions in a short time window | | Cookie stuffing indicators | Signs of forced cookie injection | ### Trust Tiers Affiliates are assigned trust tiers based on their history: | Tier | Behavior | | ------------ | ------------------------------------------------ | | **New** | All conversions require manual review | | **Trusted** | Conversions auto-approved, flags still generated | | **Verified** | Full auto-approval, reduced monitoring | Affiliates move between tiers automatically based on their track record. You can also manually adjust tiers. ## Viewing Flags ### In the Dashboard Go to your program dashboard → **Fraud Flags** to see all open flags with details about why they were raised. ### Via API ```javascript Node.js theme={null} // List open flags const flags = await client.flags.list({ programId: 'prg_abc123', status: 'open', }) // Get flag statistics const stats = await client.flags.stats({ programId: 'prg_abc123', }) ``` ```python Python theme={null} flags = client.flags.list( program_id="prg_abc123", status="open", ) stats = client.flags.stats(program_id="prg_abc123") ``` ```bash cURL theme={null} # List flags curl "https://www.agentref.co/api/v1/flags?programId=prg_abc123&status=open" \ -H "Authorization: Bearer ak_live_YOUR_KEY" # Flag stats curl "https://www.agentref.co/api/v1/flags/stats?programId=prg_abc123" \ -H "Authorization: Bearer ak_live_YOUR_KEY" ``` ## Resolving Flags Review each flag and decide whether to dismiss it or take action: ```javascript Node.js theme={null} // Dismiss a flag (false positive) await client.flags.resolve('flag_abc123', { resolution: 'dismissed', }) // Confirm fraud and block the affiliate await client.flags.resolve('flag_abc123', { resolution: 'confirmed', }) ``` ```bash cURL theme={null} curl -X POST "https://www.agentref.co/api/v1/flags/flag_abc123/resolve" \ -H "Authorization: Bearer ak_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"resolution": "dismissed"}' ``` ### Resolution Options | Resolution | Effect | | ----------- | ---------------------------------------------------------------- | | `dismissed` | Flag closed, no action taken (false positive) | | `confirmed` | Flag confirmed as fraud – associated conversions can be reversed | ## Webhook Integration Subscribe to `flag.resolved` webhook events to keep your systems in sync: ```json theme={null} { "type": "flag.resolved", "data": { "id": "flag_abc123", "affiliateId": "aff_def456", "type": "suspicious_click_pattern", "resolution": "dismissed", "resolvedAt": "2026-03-23T14:00:00.000Z" } } ``` Combine fraud detection with the [Webhooks](/docs/webhooks/overview) integration to automatically update your CRM or alert your team when flags are raised. # Install Tracking Source: https://agentref.co/docs/merchant-guide/install-tracking Add the AgentRef tracking script to your website to capture affiliate clicks and attribute conversions. The tracking script is a lightweight JavaScript snippet that runs on your domain. It captures affiliate referral codes from URL parameters, sets first-party attribution cookies, and passes referral metadata to Stripe during checkout. ## Quick install Add this script tag to every page where you want to track affiliate visits. Place it in your `` or before the closing `` tag: ```html theme={null} ``` Replace `YOUR_PROGRAM_ID` with your program's ID from the dashboard or API. That's it. For most setups, this single script tag handles everything. ## Framework examples ```html Plain HTML theme={null} My App ``` ```tsx Next.js (App Router) theme={null} // app/layout.tsx import Script from 'next/script'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ``` Do not ship `data-debug="true"` to production. It logs attribution state to the browser console on every page visit, which is noisy for real users and may expose referral context on shared machines. ## What debug mode shows The console output is the return value of `window.AgentRef.getDebugInfo()`. Here is an example: ```json theme={null} { "params": { "referralParam": "ref", "referralCode": "abc123", "clickToken": null, "debug": true, "subIds": { "sub1": null, "sub2": null, "sub3": null, "sub4": null, "sub5": null }, "utm": { "utmSource": "twitter", "utmMedium": "social", "utmCampaign": null, "utmTerm": null, "utmContent": null }, "adClickIds": { "gclid": null, "fbclid": null, "msclkid": null, "ttclid": null, "liFatId": null } }, "cookies": { "clickToken": "clk_a1b2c3d4e5", "source": "script_direct", "programId": "550e8400-e29b-41d4-a716-446655440000", "visitorId": "v_k8m2n4p6q7", "timestamp": "1711234567890" }, "lastResolution": { "clickToken": "clk_a1b2c3d4e5", "programId": "550e8400-e29b-41d4-a716-446655440000", "source": "script_direct" }, "trackingScope": { "trackingDomain": "yoursite.com", "mode": "root" }, "warnings": [] } ``` ### Field reference | Field | What it tells you | | --------------------- | ------------------------------------------------------------------------------------- | | `params.referralCode` | The affiliate code detected in the URL (`null` if none) | | `params.clickToken` | Pre-existing click token from a redirect URL (`null` for direct clicks) | | `cookies.clickToken` | The `agentref_cid` cookie value – this is what goes into Stripe metadata | | `cookies.programId` | The `agentref_pid` cookie – confirms which program the current attribution belongs to | | `trackingScope.mode` | `"root"` = cookies scoped to root domain; `"host"` = scoped to exact hostname only | | `warnings` | Array of warning codes if anything went wrong (empty = clean init) | ### Warning codes | Warning | Meaning | | ------------------------------------ | ---------------------------------------------------------------- | | `consent_pending` | Consent is required but not yet granted – tracking was skipped | | `host_outside_website_domain` | The current hostname doesn't match your program's website domain | | `cleanup_failed` | URL parameter cleanup via `history.replaceState` failed | | `stripe_payment_link_rewrite_failed` | Could not append `client_reference_id` to a Stripe Payment Link | | `direct_click_failed` | The click recording request to AgentRef failed | | `tracking_verify_failed` | The verification request failed | ## Calling `getDebugInfo()` manually You can call it any time from the browser console: ```js theme={null} window.AgentRef.getDebugInfo() ``` This is useful after navigation, after granting consent, or after a manual `AgentRef.refresh()` call – any time you want to see the current state without reloading the page. ## Troubleshooting checklist 1. Open the Network tab in DevTools and filter for `script.js`. Check the response status. 2. A `404` means your `?pid=` value is invalid or the program does not exist. Verify the program ID in your AgentRef dashboard. 3. A `400` response with `// Missing program ID` means the `?pid=` query string is missing entirely. 4. Check for Content Security Policy (CSP) headers blocking `https://www.agentref.co`. Add it to your `script-src` and `connect-src` directives. 1. Enable debug mode and check `params.referralCode` – if it's `null`, no referral parameter was found in the URL. Make sure your affiliate link includes `?via=` or another supported alias. 2. Check `warnings` – `consent_pending` means consent mode is enabled and the visitor hasn't granted consent yet. 3. Check `trackingScope.mode` – if it's `"host"` and your checkout is on a subdomain, cookies may not be shared across subdomains. Set your website URL in program settings to the root domain (e.g., `yoursite.com` not `www.yoursite.com`). 4. If the referral code is present but no click token is in cookies, the click recording request may have failed. Check the Network tab for a failed POST to `/api/tracking/click`. 1. Verify that `getCheckoutMetadata()` returns a non-empty object before checkout. Call it from the console: `window.AgentRef.getCheckoutMetadata()`. 2. Confirm the metadata is being passed to Stripe. Check the session object in your Stripe dashboard – look for `agentref_cid` in the metadata fields. 3. For Payment Links, confirm the link on your page has `data-agentref-instrumented="true"` after the script loads – this attribute is set by the script when it successfully instruments a link. 4. Check that your Stripe Connect integration is active in AgentRef. If the webhook is not receiving events, no conversions will be created. AgentRef uses last-click attribution. If a visitor clicked multiple affiliate links before purchasing, the most recent click wins. Coupon codes take priority over click tokens – if the customer uses a coupon linked to a different affiliate, that affiliate gets credit regardless of which link the visitor clicked. Check the conversion detail in your dashboard to see which attribution method was used (click token vs. coupon code). # How Tracking Works Source: https://agentref.co/docs/tracking/how-tracking-works Conceptual overview of AgentRef's click-to-commission tracking pipeline – cookie-based attribution, Stripe webhook matching, and the full conversion flow. AgentRef uses a lightweight, first-party JavaScript snippet combined with Stripe webhooks to track the entire affiliate journey. This page explains the full pipeline from click to commission. ## The tracking pipeline ```mermaid theme={null} graph TD A["Affiliate shares referral link"] --> B["Visitor clicks link"] B --> C["Tracking script loads on merchant site"] C --> D["Script records click + sets first-party cookies"] D --> E["Visitor browses the site"] E --> F["Visitor purchases via Stripe"] F --> G["Stripe webhook fires to AgentRef"] G --> H["AgentRef matches metadata to click"] H --> I["Conversion created, commission calculated"] ``` ## How clicks are tracked When a visitor arrives on your site through an affiliate link (e.g., `https://yoursite.com?via=abc123`), the tracking script: 1. **Detects the referral parameter.** Generated links use `via` by default. The script also accepts `ref`, `r`, `a`, and any [custom aliases](/docs/tracking/javascript-snippet#url-parameters) you configure. 2. **Records the click server-side.** The script sends a request to AgentRef's click endpoint with the referral code, page URL, UTM data, and ad-click IDs. 3. **Sets first-party cookies.** On successful click recording, the script stores attribution data in cookies on your domain. 4. **Cleans up the URL.** The referral parameter is removed from the browser address bar so it doesn't leak into bookmarks or shared links. ## Cookie details The tracking script sets the following first-party cookies on the merchant's domain: | Cookie | Purpose | Example value | | -------------- | ----------------------------------- | ------------------- | | `agentref_cid` | Click token for conversion matching | `clk_a1b2c3d4e5` | | `agentref_src` | Traffic source identifier | `script_direct` | | `agentref_pid` | Program ID | `550e8400-e29b-...` | | `agentref_vid` | Anonymous visitor ID | `v_k8m2n4p6...` | | `agentref_ts` | Timestamp of cookie creation | `1711234567890` | **Key characteristics:** * **First-party only.** Cookies are set on your domain (e.g., `.yoursite.com`), not a third-party domain. This means they work reliably across browsers, including Safari's ITP restrictions. * **Configurable duration.** Default is 30 days. You can change this per program in your program settings. * **Root domain scoping.** Cookies are scoped to your root domain, so they persist across subdomains (e.g., `www.yoursite.com` and `app.yoursite.com`). * **Secure attributes.** On HTTPS sites, cookies include the `Secure` flag. All cookies use `SameSite=Lax`. ## Attribution logic AgentRef uses **last-click attribution** – the industry-standard model for affiliate programs. When a payment comes in, AgentRef resolves the affiliate using this priority chain: | Priority | Method | How it works | | -------- | --------------- | ------------------------------------------------------------------------------------------------------------- | | 1 | **Coupon code** | If the customer used a coupon linked to an affiliate, that affiliate gets credit regardless of click history. | | 2 | **Click token** | The `agentref_cid` from session metadata or `client_reference_id` is matched to the original click record. | If multiple affiliates drove clicks for the same visitor, the most recent click before conversion wins. Coupon-based attribution always takes priority over click-based attribution. This means an affiliate with a coupon code gets credit even if the visitor originally arrived through a different affiliate's link. ## How conversions are created Once a Stripe payment event fires, AgentRef processes it through the webhook pipeline: Stripe sends `checkout.session.completed` (for new purchases) or `invoice.paid` (for subscription renewals) to AgentRef's Connect webhook endpoint. AgentRef reads `agentref_cid`, `agentref_pid`, and `agentref_source` from Stripe metadata for custom checkout flows. For hosted Stripe surfaces (`Payment Links`, `Buy Buttons`, `Pricing Tables`), it uses `client_reference_id` as the click-token anchor. Using the attribution priority chain above, AgentRef identifies which affiliate (if any) drove the sale. A conversion record is created with status `pending`. The commission amount is calculated based on your program's rate settings. Automated fraud detection (duplicate clicks, self-referral, geographic anomalies) runs on the conversion. Suspicious conversions are flagged for review. ## Integration points | Component | What it does | Required? | | ------------------------------------------------ | ------------------------------------------------ | -------------------------- | | [Tracking script](/docs/tracking/javascript-snippet) | Captures clicks and sets cookies on your domain | Yes | | [Stripe Connect](/docs/merchant-guide/connect-stripe) | Sends payment events to AgentRef via webhooks | Yes | | [Checkout metadata](/docs/tracking/stripe-checkout) | Passes attribution cookies into Stripe sessions | Depends on checkout method | | [Consent mode](/docs/tracking/consent-gdpr) | Respects GDPR consent before setting cookies | Optional | | [Server-side tracking](/docs/tracking/server-side) | API-based click recording without the JS snippet | Optional | | [Debug mode](/docs/tracking/debug-mode) | Inspect tracking state and troubleshoot issues | Optional | ## What's next Install the tracking script on your site. Connect tracking to your Stripe checkout flow. # JavaScript Snippet Source: https://agentref.co/docs/tracking/javascript-snippet Install the AgentRef tracking script on your site and configure URL parameters, UTM tracking, sub-IDs, and the DOM API. The tracking script is a single ` ``` Replace `YOUR_PROGRAM_ID` with the UUID from your program's **Tracking** settings page. Use `next/script` with `strategy="afterInteractive"` so the script loads after hydration without blocking the initial render: ```tsx theme={null} // app/layout.tsx import Script from 'next/script'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ``` You can also trigger debug mode without touching the script tag by adding `?agentref_debug=1` to any URL on your site. ## URL parameters ### Referral parameter (`via`) Generated AgentRef links use the `via` URL parameter by default: ``` https://yoursite.com?via=abc123 ``` Inbound attribution always accepts `via`, `ref`, `r`, and `a`. You can configure additional aliases (for example `aff` or `partner`) in your program's tracking settings. The script checks the default parameters first, then your configured aliases, and uses the first one that has a value. ### UTM parameters All standard UTM parameters are captured and stored alongside the click record: | Parameter | Example | | -------------- | ----------------------------- | | `utm_source` | `utm_source=newsletter` | | `utm_medium` | `utm_medium=email` | | `utm_campaign` | `utm_campaign=spring_launch` | | `utm_term` | `utm_term=affiliate+software` | | `utm_content` | `utm_content=cta_button` | ### Ad-click IDs The script captures ad platform click IDs automatically when present: | Parameter | Platform | | ----------- | ------------------- | | `gclid` | Google Ads | | `fbclid` | Meta / Facebook Ads | | `msclkid` | Microsoft Ads | | `ttclid` | TikTok Ads | | `li_fat_id` | LinkedIn Ads | ### Sub-IDs Sub-IDs let affiliates pass custom tracking data through their links for their own attribution needs. Five slots are available: ``` https://yoursite.com?via=abc123&sub1=campaign_a&sub2=email_list&sub3=variant_b ``` Sub-IDs are stored with the click record and appear in conversion data returned to the affiliate via the API. ## Cookies The script sets the following first-party cookies on your domain: | Cookie | Purpose | Duration | | -------------- | -------------------------------------------------- | --------------------------------- | | `agentref_cid` | Click token – the key for conversion matching | Program setting (default 30 days) | | `agentref_src` | Traffic source (e.g., `script_direct`, `redirect`) | Program setting (default 30 days) | | `agentref_pid` | Program ID | Program setting (default 30 days) | | `agentref_vid` | Anonymous visitor ID (365-day lifetime) | 365 days | | `agentref_ts` | Timestamp of when attribution was set | Program setting (default 30 days) | **Cookie characteristics:** * **First-party only.** Cookies are set on your domain, not `agentref.co`. They are not affected by third-party cookie blocking. * **Root domain scoping.** Cookies are scoped to `.yoursite.com` so they persist across subdomains (`www`, `app`, `checkout`). * **`SameSite=Lax`.** Compatible with standard browser security policies. `Secure` flag is added automatically on HTTPS sites. ## DOM API (`window.AgentRef`) After the script loads, `window.AgentRef` is available with the following methods: ### `AgentRef.ready(callback)` Run code after tracking has initialized. Use this when you need attribution data at checkout time: ```js theme={null} window.AgentRef.ready(function() { const meta = window.AgentRef.getCheckoutMetadata(); if (meta.agentref_cid) { // Pass to your checkout API } }); ``` ### `AgentRef.getCheckoutMetadata()` Returns an object to embed in your Stripe checkout session metadata. Returns `{}` if no active attribution is found. ```js theme={null} window.AgentRef.getCheckoutMetadata(); // { // agentref_cid: "clk_a1b2c3d4e5", // agentref_pid: "550e8400-e29b-41d4-a716-446655440000", // agentref_source: "script_direct" // } ``` ### `AgentRef.getState()` Returns the current runtime state of the tracker: ```js theme={null} window.AgentRef.getState(); // { // ready: true, // clickToken: "clk_a1b2c3d4e5", // programId: "550e8400-...", // visitorId: "v_k8m2n4p6...", // consentRequired: false, // consentGranted: true, // source: "script_direct", // domainMode: "root" // } ``` ### `AgentRef.refresh()` Re-run the tracking initialization. Useful after granting cookie consent: ```js theme={null} AgentRef.setConsent('granted').then(function() { // Tracking has been re-initialized with consent }); ``` ### `AgentRef.setConsent(status)` Set the user's consent status. Pass `'granted'` to enable tracking or `'denied'` to clear cookies and disable tracking. See [Consent & GDPR](/docs/tracking/consent-gdpr) for full details. ### `AgentRef.getDebugInfo()` Returns a diagnostic snapshot. See [Debug Mode](/docs/tracking/debug-mode) for details. # Server-Side Tracking Source: https://agentref.co/docs/tracking/server-side Record referral clicks server-side when JavaScript tracking isn't available. Use server-side tracking when the JavaScript snippet isn't an option, such as mobile apps, server-rendered redirects, or checkout flows where your backend owns the attribution handoff. Server-side tracking records the **click** and returns a `clickToken`. AgentRef does not expose a public `POST /api/v1/conversions` endpoint. Conversions are created by supported payment integrations, primarily Stripe, when the payment event includes the click token in metadata or `client_reference_id`. ## When to Use * **Custom payment flows** that don't use Stripe Checkout * **Mobile apps** or native clients * **Server-rendered redirects** where you intercept the affiliate link yourself ## Workflow When a visitor arrives with `?via=AFFILIATE_CODE`, extract the referral code server-side. Inbound attribution also accepts `ref`, `r`, `a`, and configured aliases. Call `POST /api/tracking/click` with the program ID, page URL, and referral code. The response includes a `clickToken`. Persist the `clickToken` as `agentref_cid` and the program ID as `agentref_pid` in your session, database, or cookie. When the visitor converts through Stripe, pass `agentref_cid`, `agentref_pid`, and `agentref_source` into Stripe metadata, or use `client_reference_id` for hosted Stripe surfaces. ## Record a Click ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/tracking/click \ -H "Content-Type: application/json" \ -H "Origin: https://yourapp.com" \ -d '{ "programId": "550e8400-e29b-41d4-a716-446655440000", "pageUrl": "https://yourapp.com/?via=john", "referralCode": "john", "referrer": "https://blog.example.com/review", "consentGranted": true }' ``` ```javascript Node.js theme={null} const response = await fetch('https://www.agentref.co/api/tracking/click', { method: 'POST', headers: { 'Content-Type': 'application/json', // Must match the configured website domain for the program. Origin: 'https://yourapp.com', }, body: JSON.stringify({ programId: '550e8400-e29b-41d4-a716-446655440000', pageUrl: 'https://yourapp.com/?via=john', referralCode: 'john', referrer: 'https://blog.example.com/review', consentGranted: true, }), }) const { clickToken, programId } = await response.json() // Store clickToken as agentref_cid and programId as agentref_pid. ``` ```python Python theme={null} import requests resp = requests.post( "https://www.agentref.co/api/tracking/click", headers={"Origin": "https://yourapp.com"}, json={ "programId": "550e8400-e29b-41d4-a716-446655440000", "pageUrl": "https://yourapp.com/?via=john", "referralCode": "john", "referrer": "https://blog.example.com/review", "consentGranted": True, }, ) click_token = resp.json()["clickToken"] ``` **Response:** ```json theme={null} { "status": "ok", "affiliateCode": "john", "programId": "550e8400-e29b-41d4-a716-446655440000", "clickToken": "clk_a1b2c3d4e5", "cookieDurationDays": 30, "cookieDomain": "yourapp.com", "cleanupParams": ["ref"], "warnings": [] } ``` ## Verify Tracking (Optional) Confirm that a page can verify tracking for a program. This records an install-verification heartbeat; it does not require or accept a click token: ```bash theme={null} curl -X POST https://www.agentref.co/api/tracking/verify \ -H "Content-Type: application/json" \ -H "Origin: https://yourapp.com" \ -d '{ "programId": "550e8400-e29b-41d4-a716-446655440000", "pageUrl": "https://yourapp.com/pricing", "consentGranted": true }' ``` ## Pass Attribution to Stripe When the visitor converts through Stripe, pass the stored click token into the same metadata fields used by the JavaScript snippet: ```javascript Node.js theme={null} const agentrefMeta = { agentref_cid: clickToken, agentref_pid: programId, agentref_source: 'server_side', } const session = await stripe.checkout.sessions.create({ mode: 'payment', line_items: [{ price: 'price_123', quantity: 1 }], success_url: 'https://yourapp.com/success', cancel_url: 'https://yourapp.com/cancel', metadata: agentrefMeta, }) // For subscription checkout sessions, also set: // subscription_data: { metadata: agentrefMeta } ``` ```python Python theme={null} agentref_meta = { "agentref_cid": click_token, "agentref_pid": program_id, "agentref_source": "server_side", } session = stripe.checkout.Session.create( mode="payment", line_items=[{"price": "price_123", "quantity": 1}], success_url="https://yourapp.com/success", cancel_url="https://yourapp.com/cancel", metadata=agentref_meta, ) # For subscription checkout sessions, also set: # subscription_data={"metadata": agentref_meta} ``` ```javascript Hosted Stripe surfaces theme={null} // For Payment Links, Buy Buttons, and Pricing Tables, use the click token // as the Stripe client_reference_id when your integration allows it. const clientReferenceId = clickToken ``` The click and verify endpoints are unauthenticated, but they validate `Origin` or `Referer` against the program's configured website domain. For backend calls, forward or set an origin that matches your configured site. For more checkout examples, see [Stripe Checkout](/docs/tracking/stripe-checkout). # Stripe Checkout Integration Source: https://agentref.co/docs/tracking/stripe-checkout Pass affiliate attribution into Stripe sessions so AgentRef can match payments to clicks and create commissions automatically. AgentRef resolves conversions by finding the click token (`agentref_cid`) on a Stripe payment event. How you get that token into Stripe depends on which Stripe surface you use. ## Choose the checkout route first Before editing code, decide which route applies: * **Route A — Hosted Stripe on the AgentRef website:** Payment Link, Buy Button, or Pricing Table is on the website where AgentRef is installed. * **Route B — Website custom checkout:** Buy button is on the AgentRef website and calls your backend to create Stripe Checkout. * **Route C — External app checkout:** Buy button is in a Chrome extension, desktop app, mobile app, external dashboard, or another surface where AgentRef is not installed. If Route C applies, do not stop at adding metadata support to the external app request. Route checkout through the AgentRef website first so the website can read the first-party AgentRef cookie before creating the Stripe Checkout Session. ## How `getCheckoutMetadata()` works The tracking script stores a `agentref_cid` click token in a first-party cookie when a visitor arrives through an affiliate link. At checkout time, your code reads that token and embeds it into the Stripe session before the customer pays. ```js theme={null} // Returns { agentref_cid, agentref_pid, agentref_source } // Returns {} if no active attribution exists (visitor came directly) const meta = window.AgentRef.getCheckoutMetadata(); ``` When Stripe fires `checkout.session.completed` or `invoice.paid`, AgentRef's webhook reads the metadata and matches the payment to the original click. If `getCheckoutMetadata()` returns an empty object, the visitor has no affiliate attribution. You can still call your checkout API – AgentRef will simply not create a conversion for that payment. ## Integration by Stripe surface This is the most common integration – you create a Checkout Session server-side and redirect the customer to it. **Step 1:** Read the tracking metadata on your frontend: ```js theme={null} window.AgentRef.ready(async function() { const trackingMeta = window.AgentRef.getCheckoutMetadata(); const response = await fetch('/api/create-checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId: 'price_xxx', agentrefMeta: trackingMeta, }), }); const { url } = await response.json(); window.location.href = url; }); ``` **Step 2:** Pass the metadata into the Stripe session on your backend: ```js Node.js theme={null} const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); app.post('/api/create-checkout', async (req, res) => { const { priceId, agentrefMeta } = req.body; const session = await stripe.checkout.sessions.create({ mode: 'subscription', line_items: [{ price: priceId, quantity: 1 }], success_url: 'https://yoursite.com/success', cancel_url: 'https://yoursite.com/pricing', // Pass AgentRef metadata – this is what the webhook reads metadata: { ...agentrefMeta, }, }); res.json({ url: session.url }); }); ``` ```python Python theme={null} import stripe import json from flask import request, jsonify stripe.api_key = os.environ["STRIPE_SECRET_KEY"] @app.route("/api/create-checkout", methods=["POST"]) def create_checkout(): data = request.get_json() price_id = data.get("priceId") agentref_meta = data.get("agentrefMeta", {}) session = stripe.checkout.Session.create( mode="subscription", line_items=[{"price": price_id, "quantity": 1}], success_url="https://yoursite.com/success", cancel_url="https://yoursite.com/pricing", metadata=agentref_meta, ) return jsonify({"url": session.url}) ``` If you use Stripe Payment Links (links to `buy.stripe.com`), the tracking script instruments them **automatically**. No backend changes needed. When the script detects an active click token, it appends `?client_reference_id=` to any `buy.stripe.com` link on the page using a `MutationObserver`. AgentRef reads this field from the Stripe webhook. Make sure your tracking script is loaded before the customer can click the payment link. The standard `async` script tag is sufficient – the observer fires on DOM mutations so late-rendered links are also covered. Automatic Payment Link instrumentation only works when the tracking script is on the same page as the link. If you link to your Stripe Payment Link from a third-party site or email, use [Server-side tracking](/docs/tracking/server-side) instead. Stripe's embedded `` web component is also instrumented automatically. The script sets the `client-reference-id` attribute on the element when a click token is present. ```html theme={null} ``` The result is equivalent to setting it manually: ```html theme={null} ``` The script uses a `MutationObserver` to catch pricing tables that render after the page loads (e.g., in SPAs). You do not need to wait for the script to load before rendering the pricing table. If you use Stripe.js or the Payment Intents API directly, read `window.AgentRef.getCheckoutMetadata()` on the client, send it to your server, and embed it into the PaymentIntent or Customer metadata: ```js Node.js theme={null} app.post('/api/pay', async (req, res) => { const { agentrefMeta } = req.body; const paymentIntent = await stripe.paymentIntents.create({ amount: 2900, currency: 'usd', metadata: { ...(agentrefMeta || {}), }, }); res.json({ clientSecret: paymentIntent.client_secret }); }); ``` ```python Python theme={null} @app.route("/api/pay", methods=["POST"]) def pay(): data = request.get_json() metadata = data.get("agentrefMeta", {}) payment_intent = stripe.PaymentIntent.create( amount=2900, currency="usd", metadata=metadata, ) return jsonify({"clientSecret": payment_intent["client_secret"]}) ``` If the buy button lives outside the website where the AgentRef script is installed, route it through your website before sending the user to Stripe. ```txt theme={null} Chrome extension or external app -> https://yoursite.com/checkout/start?plan=pro -> your website reads AgentRef metadata -> your backend creates Stripe Checkout with that metadata -> redirect to Stripe ``` The checkout start page should run on the same root domain as your AgentRef script. A typical implementation renders a minimal page, calls `window.AgentRef.getCheckoutMetadata()`, posts the metadata to your existing checkout endpoint, and redirects to the returned Stripe Checkout URL. If an AI coding agent needs to reroute an existing buy button to this website checkout start page, it should explain the change and ask you before editing. Do not let it silently restructure checkout. ## Verifying attribution After completing a test purchase, you can confirm attribution was captured correctly by checking the conversion in your AgentRef dashboard. The conversion will appear under **Conversions** with status `pending`. To verify programmatically, look at the Stripe session's metadata in the Stripe dashboard – you should see `agentref_cid`, `agentref_pid`, and `agentref_source` fields populated for custom checkout flows. For Payment Links, Buy Buttons, and Pricing Tables, verify that Stripe received the click token via `client_reference_id`. Use [Debug Mode](/docs/tracking/debug-mode) to inspect cookies and confirm `agentref_cid` is set before triggering a test checkout. ## What happens without metadata If a Stripe payment event arrives without `agentref_cid` in the metadata (and without a `client_reference_id` for Payment Links/Pricing Tables), AgentRef will not create a conversion for that payment. The payment is not affected – only the affiliate attribution is skipped. Coupon-based attribution does not require metadata. If the customer uses a coupon code linked to an affiliate, a conversion is created regardless of whether tracking cookies were set. # Webhook Events Source: https://agentref.co/docs/webhooks/events All 13 webhook event types with payload examples. AgentRef sends 13 event types grouped into 5 categories. Subscribe only to the events you need when creating a webhook endpoint. Marketing Resources lifecycle changes do not currently emit webhook events. ## Envelope Format Every event is wrapped in the same envelope: ```json theme={null} { "id": "msg_abc123", "type": "conversion.created", "schemaVersion": 2, "createdAt": "2026-03-23T14:30:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": {} } ``` | Field | Type | Description | | --------------- | --------- | ----------------------------------------- | | `id` | `string` | Unique message ID (use for idempotency) | | `type` | `string` | Event type (see below) | | `schemaVersion` | `2` | Always `2` | | `createdAt` | `string` | ISO 8601 timestamp | | `merchantId` | `string` | Your merchant ID | | `programId` | `string?` | Program ID (if event is program-scoped) | | `environment` | `string` | `production`, `preview`, or `development` | | `data` | `object` | Event-specific payload | *** ## Program Events Fired when a new affiliate program is created. ```json theme={null} { "id": "msg_001", "type": "program.created", "schemaVersion": 2, "createdAt": "2026-03-23T14:30:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "prg_xyz789", "name": "Pro Plan Affiliates", "commissionType": "recurring", "commissionPercent": 30, "cookieDuration": 90, "status": "active" } } ``` Fired when program settings are changed (name, commission, cookie duration, etc.). ```json theme={null} { "id": "msg_002", "type": "program.updated", "schemaVersion": 2, "createdAt": "2026-03-23T14:31:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "prg_xyz789", "name": "Pro Plan Affiliates", "commissionType": "recurring", "commissionPercent": 35, "cookieDuration": 90, "status": "active", "updatedFields": ["commissionPercent"] } } ``` ## Affiliate Events Fired when an affiliate joins your program (via invite link or marketplace). ```json theme={null} { "id": "msg_003", "type": "affiliate.joined", "schemaVersion": 2, "createdAt": "2026-03-23T15:00:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "aff_def456", "programId": "prg_xyz789", "email": "affiliate@example.com", "name": "Jane Smith", "code": "jane", "joinSource": "invite", "isApproved": false, "isBlocked": false, "joinedAt": "2026-03-23T15:00:00.000Z", "approvedAt": null, "blockedAt": null } } ``` Fired when a pending affiliate is approved. ```json theme={null} { "id": "msg_004", "type": "affiliate.approved", "schemaVersion": 2, "createdAt": "2026-03-23T15:05:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "aff_def456", "programId": "prg_xyz789", "email": "affiliate@example.com", "name": "Jane Smith", "code": "jane", "isApproved": true, "isBlocked": false, "approvedAt": "2026-03-23T15:05:00.000Z" } } ``` Fired when an affiliate is blocked (manually or by fraud detection). ```json theme={null} { "id": "msg_005", "type": "affiliate.blocked", "schemaVersion": 2, "createdAt": "2026-03-23T16:00:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "aff_def456", "programId": "prg_xyz789", "email": "affiliate@example.com", "isApproved": true, "isBlocked": true, "blockedReason": "Suspicious click patterns detected", "blockedAt": "2026-03-23T16:00:00.000Z" } } ``` Fired when a previously blocked affiliate is reinstated. ```json theme={null} { "id": "msg_006", "type": "affiliate.unblocked", "schemaVersion": 2, "createdAt": "2026-03-23T17:00:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "aff_def456", "programId": "prg_xyz789", "email": "affiliate@example.com", "isApproved": true, "isBlocked": false, "blockedReason": null, "blockedAt": null, "updatedAt": "2026-03-23T17:00:00.000Z" } } ``` ## Conversion Events Fired when a new conversion is recorded from a supported attribution source such as Stripe metadata, Stripe `client_reference_id`, or coupon attribution. ```json theme={null} { "id": "msg_007", "type": "conversion.created", "schemaVersion": 2, "createdAt": "2026-03-23T18:00:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "conv_ghi789", "affiliateId": "aff_def456", "programId": "prg_xyz789", "clickId": "clk_abc123", "paymentProvider": "stripe", "externalId": "cs_live_abc123", "saleAmount": 9900, "refundedAmount": 0, "currency": "usd", "isRecurring": false, "recurringMonth": null, "status": "pending", "attributionMethod": "click_token", "createdAt": "2026-03-23T18:00:00.000Z" } } ``` Fired when a conversion is refunded (triggered by Stripe refund or manual action). ```json theme={null} { "id": "msg_008", "type": "conversion.refunded", "schemaVersion": 2, "createdAt": "2026-03-23T19:00:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "conv_ghi789", "affiliateId": "aff_def456", "programId": "prg_xyz789", "paymentProvider": "stripe", "externalId": "ch_abc123", "saleAmount": 9900, "refundedAmount": 9900, "currency": "usd", "status": "refunded", "updatedAt": "2026-03-23T19:00:00.000Z" } } ``` ## Payout Events Fired when a payout is created for an affiliate. ```json theme={null} { "id": "msg_009", "type": "payout.created", "schemaVersion": 2, "createdAt": "2026-03-24T10:00:00.000Z", "merchantId": "mer_abc123", "environment": "production", "data": { "id": "pay_jkl012", "affiliateId": "aff_def456", "programId": "prg_xyz789", "amount": 15000, "feeAmount": 0, "netAmount": 15000, "currency": "usd", "method": "paypal", "status": "pending", "periodStart": "2026-03-01T00:00:00.000Z", "periodEnd": "2026-03-31T23:59:59.000Z", "createdAt": "2026-03-24T10:00:00.000Z" } } ``` Fired when a payout moves into the manual processing step after the merchant starts the external payment. ```json theme={null} { "id": "msg_010", "type": "payout.processing", "schemaVersion": 2, "createdAt": "2026-03-24T10:05:00.000Z", "merchantId": "mer_abc123", "environment": "production", "data": { "id": "pay_jkl012", "affiliateId": "aff_def456", "programId": "prg_xyz789", "amount": 15000, "netAmount": 15000, "currency": "usd", "method": "paypal", "status": "processing" } } ``` Fired when a payout is marked completed after the external payment succeeds. ```json theme={null} { "id": "msg_011", "type": "payout.completed", "schemaVersion": 2, "createdAt": "2026-03-24T12:00:00.000Z", "merchantId": "mer_abc123", "environment": "production", "data": { "id": "pay_jkl012", "affiliateId": "aff_def456", "programId": "prg_xyz789", "amount": 15000, "netAmount": 15000, "currency": "usd", "method": "paypal", "status": "completed", "processedAt": "2026-03-24T10:05:00.000Z", "completedAt": "2026-03-24T12:00:00.000Z" } } ``` Fired when a manual payout attempt fails and the payout is marked for retry or follow-up. ```json theme={null} { "id": "msg_012", "type": "payout.failed", "schemaVersion": 2, "createdAt": "2026-03-24T12:00:00.000Z", "merchantId": "mer_abc123", "environment": "production", "data": { "id": "pay_jkl012", "affiliateId": "aff_def456", "programId": "prg_xyz789", "amount": 15000, "netAmount": 15000, "currency": "usd", "method": "paypal", "status": "failed", "failureReason": "External payment rejected by provider" } } ``` ## Fraud Events Fired when a fraud flag is resolved (approved or rejected). ```json theme={null} { "id": "msg_013", "type": "flag.resolved", "schemaVersion": 2, "createdAt": "2026-03-24T14:00:00.000Z", "merchantId": "mer_abc123", "programId": "prg_xyz789", "environment": "production", "data": { "id": "flag_mno345", "affiliateId": "aff_def456", "type": "suspicious_click_pattern", "resolution": "dismissed", "resolvedAt": "2026-03-24T14:00:00.000Z", "resolvedBy": "merchant" } } ``` ## Quick Reference | Event | Category | When | | --------------------- | ---------- | ------------------------- | | `program.created` | Program | New program created | | `program.updated` | Program | Program settings changed | | `affiliate.joined` | Affiliate | Affiliate joins program | | `affiliate.approved` | Affiliate | Affiliate approved | | `affiliate.blocked` | Affiliate | Affiliate blocked | | `affiliate.unblocked` | Affiliate | Affiliate reinstated | | `conversion.created` | Conversion | New conversion recorded | | `conversion.refunded` | Conversion | Conversion refunded | | `payout.created` | Payout | Payout created | | `payout.processing` | Payout | Payout processing started | | `payout.completed` | Payout | Payout marked completed | | `payout.failed` | Payout | Payout failed | | `flag.resolved` | Fraud | Fraud flag resolved | # Webhooks Overview Source: https://agentref.co/docs/webhooks/overview Get notified in real time when affiliate, conversion, and payout events happen in your programs. Webhooks let your backend react to AgentRef events without polling the API. When something interesting happens – an affiliate joins, a conversion is created, a payout completes – AgentRef sends an HTTP POST to your endpoint with a signed JSON payload. ## How it works 1. You register an HTTPS endpoint (your server URL) 2. You choose which event types to subscribe to 3. AgentRef sends a signed `POST` request to your URL every time a matching event fires 4. Your server responds with a `2xx` status to acknowledge receipt 5. If delivery fails, AgentRef retries automatically on an exponential backoff schedule ## Webhook envelope Every webhook delivery shares the same outer envelope regardless of event type: ```json theme={null} { "id": "msg_01jq2k3n4p5q6r7s8t9u0v1w2x", "type": "conversion.created", "schemaVersion": 2, "createdAt": "2026-03-15T14:22:00.000Z", "merchantId": "mch_01jq2abc", "programId": "prg_01jq2def", "environment": "production", "data": { "id": "conv_01jq2ghi", "affiliateId": "aff_01jq2jkl", "saleAmount": 9900, "currency": "usd", "status": "pending" } } ``` | Field | Description | | --------------- | -------------------------------------------------------------------- | | `id` | Unique message ID. Use this for idempotency. | | `type` | Event type string, e.g. `conversion.created` | | `schemaVersion` | Always `2` for current payloads | | `createdAt` | ISO 8601 timestamp when the event was created | | `merchantId` | Your merchant ID | | `programId` | Program ID the event belongs to (omitted for merchant-scoped events) | | `environment` | `production`, `preview`, or `development` | | `data` | Event-specific payload (see [Events](/docs/webhooks/events)) | ## Registering a webhook endpoint 1. Go to **Settings → Webhooks** in your dashboard 2. Click **Add endpoint** 3. Enter your endpoint URL and a descriptive name 4. Select the event types you want to receive 5. Optionally scope the endpoint to a specific program 6. Click **Create** – your signing secret is shown once; copy it now ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/v1/webhooks \ -H "Authorization: Bearer ak_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Production webhook", "url": "https://your-app.com/webhooks/agentref", "subscribedEvents": [ "conversion.created", "payout.completed" ] }' ``` ```typescript Node.js theme={null} import { AgentRef } from 'agentref'; const client = new AgentRef({ apiKey: 'ak_live_...' }); const { endpoint, signingSecret } = await client.webhooks.create({ name: 'Production webhook', url: 'https://your-app.com/webhooks/agentref', subscribedEvents: ['conversion.created', 'payout.completed'], }); // Store signingSecret securely – it is only returned once console.log(signingSecret); ``` ```python Python theme={null} from agentref import AgentRef client = AgentRef(api_key='ak_live_...') result = client.webhooks.create( name='Production webhook', url='https://your-app.com/webhooks/agentref', subscribed_events=['conversion.created', 'payout.completed'], ) # Store result.signing_secret securely – it is only returned once print(result.signing_secret) ``` The signing secret is only returned in the creation response. Store it in your secrets manager immediately. You cannot retrieve it again – only rotate it. The response includes: ```json theme={null} { "data": { "endpoint": { "id": "wh_01jq2k3n...", "name": "Production webhook", "url": "https://your-app.com/webhooks/agentref", "status": "active", "programId": null, "schemaVersion": 2, "subscribedEvents": ["conversion.created", "payout.completed"], "secretLastFour": "a3f9", "createdAt": "2026-03-15T14:00:00.000Z", "updatedAt": "2026-03-15T14:00:00.000Z", "disabledAt": null }, "signingSecret": "whsec_..." } } ``` ## Event type filtering You must subscribe to at least one event type per endpoint. You can subscribe to any subset of the [13 available events](/docs/webhooks/events). Only events in your subscription list will be delivered to that endpoint. Marketing Resources lifecycle changes do not currently emit webhook events. To update subscriptions on an existing endpoint: ```bash cURL theme={null} curl -X PATCH https://www.agentref.co/api/v1/webhooks/wh_01jq2k3n \ -H "Authorization: Bearer ak_live_..." \ -H "Content-Type: application/json" \ -d '{"subscribedEvents": ["conversion.created", "conversion.refunded", "payout.completed"]}' ``` ## Program scoping By default, a webhook endpoint receives events from **all** your programs. If you have multiple programs and want to isolate events, you can scope an endpoint to a specific program by passing `programId` when creating or updating the endpoint. A scoped endpoint only receives events where `programId` matches. Unscoped endpoints receive events from every program. ```bash cURL theme={null} curl -X POST https://www.agentref.co/api/v1/webhooks \ -H "Authorization: Bearer ak_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Program A only", "url": "https://your-app.com/webhooks/program-a", "subscribedEvents": ["affiliate.joined"], "programId": "prg_01jq2def" }' ``` ## Endpoint requirements * **HTTPS only** – HTTP endpoints are rejected in production (localhost HTTP is allowed during development) * **Respond within 30 seconds** – requests that time out are treated as failed deliveries * **Return a 2xx status** – any non-2xx response triggers a retry, including 3xx redirects * **No authentication required on your side** – verify the request using the [signature](/docs/webhooks/signature-verification) instead ## Managing endpoints via API | Method | Path | Scope required | Description | | -------- | ------------------------------------- | ---------------- | ---------------------------------- | | `GET` | `/api/v1/webhooks` | `webhooks:read` | List all endpoints | | `POST` | `/api/v1/webhooks` | `webhooks:write` | Create an endpoint | | `GET` | `/api/v1/webhooks/{id}` | `webhooks:read` | Get a single endpoint | | `PATCH` | `/api/v1/webhooks/{id}` | `webhooks:write` | Update name, URL, or subscriptions | | `DELETE` | `/api/v1/webhooks/{id}` | `webhooks:write` | Disable an endpoint | | `POST` | `/api/v1/webhooks/{id}/rotate-secret` | `webhooks:write` | Rotate the signing secret | Deleting an endpoint marks it as `disabled`. Disabled endpoints stop receiving deliveries and cannot be re-enabled via API – create a new endpoint instead. ## Next steps Verify incoming requests are genuinely from AgentRef Full payload examples for all 13 event types Understand retries, idempotency, and failure handling Test your endpoint locally before going to production # Retry & Delivery Source: https://agentref.co/docs/webhooks/retry-delivery How AgentRef retries failed webhook deliveries and how to handle them reliably. AgentRef automatically retries failed webhook deliveries with exponential backoff. Here's how to build a reliable webhook consumer. ## Retry Schedule If your endpoint doesn't respond with a `2xx` status code within 30 seconds, AgentRef retries: | Attempt | Delay | Cumulative Time | | ------- | ---------- | --------------- | | 1 | Immediate | 0 | | 2 | 1 minute | 1 min | | 3 | 5 minutes | 6 min | | 4 | 15 minutes | 21 min | | 5 | 1 hour | \~1.3 hours | | 6 | 3 hours | \~4.3 hours | | 7 | 6 hours | \~10.3 hours | | 8 | 12 hours | \~22.3 hours | | 9 | 24 hours | \~46.3 hours | After all 8 retry attempts are exhausted (9 total including the first), the message is marked as failed. ## What Counts as Failure | Scenario | Retried? | | ------------------------------------- | ------------ | | Non-2xx HTTP response (3xx, 4xx, 5xx) | Yes | | Connection timeout (>30 seconds) | Yes | | DNS resolution failure | Yes | | TLS/SSL error | Yes | | 2xx response | No (success) | ## Handling Duplicates Your endpoint may receive the same event more than once – either from retries or from concurrent deliveries. Use the `id` field in the webhook envelope for idempotency: ```javascript theme={null} const processedEvents = new Set() // Use a database in production app.post('/webhooks/agentref', (req, res) => { const event = req.body // Skip if already processed if (processedEvents.has(event.id)) { return res.status(200).send('Already processed') } // Process the event handleEvent(event) processedEvents.add(event.id) res.status(200).send('OK') }) ``` In production, store processed event IDs in your database with a unique constraint rather than an in-memory Set. ## Best Practices ### Respond Quickly Return a `200` response as soon as you receive the webhook. Process the event asynchronously: ```javascript theme={null} app.post('/webhooks/agentref', (req, res) => { // Respond immediately res.status(200).send('OK') // Process asynchronously processEventAsync(req.body).catch(console.error) }) ``` ### Use a Queue For complex processing, push webhook events to a queue (Redis, SQS, etc.) and process them with a worker: ```javascript theme={null} app.post('/webhooks/agentref', async (req, res) => { await queue.push('webhook-events', req.body) res.status(200).send('OK') }) ``` ### Monitor Endpoint Health If your endpoint consistently fails, webhook deliveries will accumulate retries. Monitor your webhook endpoint's response times and error rates. After all retries are exhausted, failed messages are **not** automatically re-sent. Check your webhook logs in the AgentRef dashboard to identify and investigate persistent failures. # Signature Verification Source: https://agentref.co/docs/webhooks/signature-verification Verify webhook signatures to ensure payloads are authentically from AgentRef. Every webhook delivery is signed with HMAC-SHA256. Verify the signature to confirm the payload came from AgentRef and wasn't tampered with. The Node.js and Python SDKs do not currently expose a webhook signature helper. Use the manual verification algorithm below, or wrap it in your own helper for your framework. ## Signature Format AgentRef sends three headers with each webhook delivery: | Header | Description | | ---------------- | -------------------------------------------------- | | `svix-id` | Unique message ID | | `svix-timestamp` | Unix timestamp (seconds) when the message was sent | | `svix-signature` | `v1,{base64_signature}` | ## Verification Algorithm Read the body as a raw string – do **not** parse JSON first. Parsing and re-serializing can change whitespace or key order, breaking the signature. Extract `svix-id`, `svix-timestamp`, and `svix-signature` from the request headers. Build the signed content string: `{svix-id}.{svix-timestamp}.{body}` Compute HMAC-SHA256 of this string using your webhook signing secret (the part after `whsec_`, base64url-decoded). Base64-encode your HMAC result and compare it to the signature value (after the `v1,` prefix) using constant-time comparison. ## Code Examples ```javascript Node.js (Express) theme={null} import crypto from 'crypto' function verifyWebhookSignature(req, secret) { const msgId = req.headers['svix-id'] const timestamp = req.headers['svix-timestamp'] const signature = req.headers['svix-signature'] // Check timestamp to prevent replay attacks (5 min tolerance) const now = Math.floor(Date.now() / 1000) if (Math.abs(now - parseInt(timestamp)) > 300) { throw new Error('Timestamp too old') } // Decode the secret (remove whsec_ prefix, base64url decode) const secretBytes = Buffer.from(secret.replace('whsec_', ''), 'base64url') // Build the signed content const signedContent = `${msgId}.${timestamp}.${req.body}` // Compute expected signature const expected = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64') // Extract actual signature (remove v1, prefix) const actual = signature.split(',')[1] // Constant-time comparison const expectedBytes = Buffer.from(expected) const actualBytes = Buffer.from(actual) if (expectedBytes.length !== actualBytes.length || !crypto.timingSafeEqual(expectedBytes, actualBytes)) { throw new Error('Invalid signature') } return JSON.parse(req.body) } // Express route app.post('/webhooks/agentref', express.raw({ type: 'application/json' }), (req, res) => { try { const event = verifyWebhookSignature(req, process.env.AGENTREF_WEBHOOK_SECRET) console.log('Verified event:', event.type) res.status(200).send('OK') } catch (err) { console.error('Verification failed:', err.message) res.status(400).send('Invalid signature') } }) ``` ```python Python (Flask) theme={null} import hmac import hashlib import base64 import json import os import time from flask import Flask, request app = Flask(__name__) def verify_webhook_signature(payload, headers, secret): msg_id = headers.get("svix-id") timestamp = headers.get("svix-timestamp") signature = headers.get("svix-signature") # Check timestamp (5 min tolerance) now = int(time.time()) if abs(now - int(timestamp)) > 300: raise ValueError("Timestamp too old") # Decode secret (remove whsec_ prefix, base64url decode) secret_bytes = base64.urlsafe_b64decode(secret.removeprefix("whsec_") + "==") # Build signed content signed_content = f"{msg_id}.{timestamp}.{payload}" # Compute expected signature expected = base64.b64encode( hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest() ).decode() # Extract actual (remove v1, prefix) actual = signature.split(",")[1] if not hmac.compare_digest(expected, actual): raise ValueError("Invalid signature") return json.loads(payload) @app.route("/webhooks/agentref", methods=["POST"]) def webhook(): try: event = verify_webhook_signature( request.get_data(as_text=True), request.headers, os.environ["AGENTREF_WEBHOOK_SECRET"], ) print(f"Verified event: {event['type']}") return "OK", 200 except ValueError as e: return str(e), 400 ``` ## Common Pitfalls **Don't parse the body before verifying.** If your framework automatically parses JSON, use a raw body parser for the webhook route. `JSON.parse(JSON.stringify(body))` may reorder keys or change whitespace, producing a different signature. * **Use `express.raw()` in Express** (not `express.json()`) for the webhook route * **In Next.js App Router**, use `await request.text()` to get the raw body * **Always use constant-time comparison** (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python) to prevent timing attacks * **Validate the timestamp** – reject events older than 5 minutes to prevent replay attacks # Testing Webhooks Source: https://agentref.co/docs/webhooks/testing How to test webhook delivery and signature verification locally. Test your webhook integration before going to production. ## Testing Locally with ngrok Your webhook endpoint needs a public URL. Use [ngrok](https://ngrok.com) to tunnel to your local server: ```bash theme={null} node server.js # Listening on http://localhost:3000 ``` ```bash theme={null} ngrok http 3000 # Forwarding: https://abc123.ngrok.io → http://localhost:3000 ``` Use the ngrok URL as your webhook endpoint: ```bash theme={null} curl -X POST https://www.agentref.co/api/v1/webhooks \ -H "Authorization: Bearer ak_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Local Testing", "url": "https://abc123.ngrok.io/webhooks/agentref", "subscribedEvents": ["conversion.created", "affiliate.joined"] }' ``` Perform an action that triggers a webhook, such as completing a Stripe test checkout with attribution metadata or approving an affiliate application. ## Simulating a Webhook with cURL To test your endpoint's signature verification, simulate a delivery: ```bash theme={null} # Your webhook secret SECRET="whsec_your_secret_here" # Build the payload BODY='{"id":"msg_test_001","type":"conversion.created","schemaVersion":2,"createdAt":"2026-03-23T18:00:00.000Z","merchantId":"mer_test","environment":"development","data":{"id":"conv_test","affiliateId":"aff_test","programId":"prg_test","paymentProvider":"stripe","externalId":"cs_test","saleAmount":9900,"refundedAmount":0,"currency":"usd","status":"pending"}}' # Generate signature MSG_ID="msg_test_001" TIMESTAMP=$(date +%s) SIGNATURE=$(echo -n "${MSG_ID}.${TIMESTAMP}.${BODY}" | openssl dgst -sha256 -hmac $(echo -n "${SECRET#whsec_}" | base64 -d) -binary | base64) # Send the request curl -X POST http://localhost:3000/webhooks/agentref \ -H "Content-Type: application/json" \ -H "svix-id: ${MSG_ID}" \ -H "svix-timestamp: ${TIMESTAMP}" \ -H "svix-signature: v1,${SIGNATURE}" \ -d "${BODY}" ``` ## Verification Checklist Before going to production, confirm: * [ ] Your endpoint returns `200` within 30 seconds * [ ] Signature verification works correctly * [ ] You handle duplicate deliveries (check event `id`) * [ ] You process events asynchronously (don't block the response) * [ ] Your endpoint uses HTTPS in production * [ ] You've subscribed only to events you need * [ ] Error handling is in place – a crash won't take down your server ## Debugging Delivery Issues If webhooks aren't arriving: 1. **Check the dashboard** – go to Settings → Webhooks to see delivery status and errors 2. **Verify the URL** – is your endpoint reachable from the public internet? 3. **Check HTTPS** – production webhooks require HTTPS (localhost HTTP is allowed in development) 4. **Check event subscription** – did you subscribe to the event types you're expecting? 5. **Check response code** – a non-2xx response triggers retries, not immediate re-delivery