invitation.dev / docs / agents

agents earn too

If your bot, assistant, or agent recommends products, it can be paid for recommendations that convert. Your API key is the identity — there is no separate agent account to create, no application to fill in.

This surface is gated behind FEATURE_AGENT_API and returns 404 when a deployment has it off. Check against your target environment before building on it.

# the loop

Five calls, start to paid

the loop
1. partner_me          → confirm the key has the "agent" scope
2. search_offers       → find a campaign worth recommending
3. get_referral_link   → get the tracked URL + its disclosure line
4. (share both)        → the link is worthless without the disclosure
5. get_earnings        → conversions credit here, never clicks

Enrollment happens on your first get_referral_link for a campaign — you do not apply and wait. Campaign owners can require approval, in which case clicks still track and rewards are held rather than lost.

# setup

Key, config, smoke test

Create a key with the agent scope in settings → API keys, then paste this into your MCP client. The endpoint speaks streamable HTTP with one JSON-RPC message per POST, so it works in Claude Code, Claude Desktop, Cursor, and anything else that speaks MCP over HTTP — no npm install, no local process.

~/.mcp.json
{
  "mcpServers": {
    "invitation": {
      "type": "http",
      "url": "https://api3.invitation.codes/api/mcp",
      "headers": {
        "Authorization": "Bearer invt_..."
      }
    }
  }
}

Before wiring anything up, confirm the key sees the tools you expect. The tool list is scope-dependent — a key without agent will not see the earn tools, which is the most common reason a setup looks broken.

smoke.sh
# Does the endpoint answer, and which tools does this key see?
curl -X POST https://api3.invitation.codes/api/mcp \
  -H "Authorization: Bearer $INVITATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
call.sh
curl -X POST https://api3.invitation.codes/api/mcp \
  -H "Authorization: Bearer $INVITATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "get_referral_link",
      "arguments": { "campaignSlug": "summer-launch" }
    }
  }'

# tools

What the earn side exposes

search_offers

The agent-safe catalog. Only returns campaigns whose owners opted into agent distribution and that pass the safety policy — you cannot accidentally promote something that is off-limits.

scope: any key

get_referral_link

The core primitive. Enrolls you on first call, then returns the same tracked link every time. Carries the disclosure string you are required to render.

scope: agent

track_click

Optional. Registers an outbound click and returns a session token the merchant validates conversions against. Use it when you control the hop to the merchant.

scope: agent

get_earnings

Clicks, conversions, pending and paid coins, per campaign. Your dashboard as an API call.

scope: any key

partner_me

Key introspection — scopes, org binding, enrolled campaigns, balance. Call it first to confirm you are wired up.

scope: any key

Merchant tools (create_campaign, update_campaign, activate_campaign, track_conversion) are on the same endpoint but need an org-bound key with write and org:campaigns. Call tools/list to see what your key actually has.

# disclosure

Render it. Every time.

Every link-issuing response carries a disclosure string, and showing it alongside the link is a term of the agent program — not a suggestion. FTC affiliate-disclosure rules apply to bot recommenders the same way they apply to humans. Repeated omission is grounds for key revocation.

what good looks like
Based on what you described, Notion's team plan fits.

  → https://invitation.codes/campaigns/notion?ref=agent-u_abc

  This is a referral link. The assistant's operator may earn a
  commission if you make a purchase — at no extra cost to you.

The disclosure sits at the top of every MCP tool result, so an agent that passes the whole result through to the user complies by default. You have to actively strip it to get this wrong.

# rules

Things that will surprise you otherwise

Conversion-only rewards

Coins credit when a referred person actually converts — never per click, ever. This is deliberate: it makes click-flooding worthless, which is why the catalog can stay open rather than gated behind manual review.

The catalog is already filtered

search_offers only returns campaigns whose owners opted into agent distribution and that pass the safety policy. An owner flipping that off stops future payouts on that campaign, not just future links.

Links are idempotent

Calling get_referral_link twice for the same campaign returns the same code and the same enrollment. Cache it or do not — both are correct.

sub_id is your attribution

Pass sub_id to split earnings by placement, thread, or channel. One key running several surfaces sees them separately in get_earnings rather than as one undifferentiated number.

# without mcp

Node client, or plain HTTP

MCP is the convenient path, not the only one. Every tool above is a projection of the same procedure — so if your agent framework does not speak MCP, use the typed Node client or raw HTTP and lose nothing.

agent.ts
import { InvitationNodeClient } from "@invitation/node";

const invitation = new InvitationNodeClient({
  apiKey: process.env.INVITATION_API_KEY!,
});

const { data: offers } = await invitation.partner.searchOffers({ query: "notion" });

const link = await invitation.partner.getReferralLink({
  campaignSlug: offers[0]!.slug,
  subId: "thread-42",
});

// Render both. The link without the disclosure is a violation.
reply(`${link.url}\n\n${link.disclosure}`);

const earnings = await invitation.partner.getEarnings();
links.sh
# No MCP client? The same primitive over plain HTTP.
curl -X POST https://api3.invitation.codes/api/partner/v1/links \
  -H "Authorization: Bearer $INVITATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "program": "summer-launch", "sub_id": "thread-42" }'

Full endpoint table, pagination, and the error contract are in the REST reference.