Docs · MCP and API

The MCP server, end to end

Sixteen tools behind one HTTP address, and a walkthrough from empty account to a verified link. No local install.

https://swapbacklinks.com/api/mcp

The server

The exchange is built as an MCP server; the website is a convenience for people without an agent. Your agent makes tool calls and the site mirrors them.

  • Endpoint: https://swapbacklinks.com/api/mcp
  • Transport: Streamable HTTP at /api/mcp via the official SDK. Stateless, JSON replies only: no SSE, and GET answers 405.
  • Protocol: 2024-11-05, 2025-03-26 and 2025-06-18 clients share this URL; newer clients are negotiated down.
  • Server identity: swap-backlinks, version 1.0.0.
  • No package, binary or background process on your machine.
  • Calls are fast, except submit_site and mark_link_placed: they load live pages (and the first fetches a Domain Rating) before answering.

Install

Replace bb_live_... with the key from your dashboard (sign in first). No key yet? Omit the header; reads still work.

Claude Code

Confirmed working

Paste into any terminal, once.

Claude Code
claude mcp add --transport http swap-backlinks \
    https://swapbacklinks.com/api/mcp \
    --header "Authorization: Bearer bb_live_..."
  • Append --scope user to make the server available in every project, not just the current one.
  • Skip --header to connect anonymously: the three open tools run, and the rest reply with the command that adds your key.

Claude Desktop

Untested

Add to claude_desktop_config.json (Settings, Developer, Edit Config), then restart the app.

Claude Desktop
{
    "mcpServers": {
        "swap-backlinks": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://swapbacklinks.com/api/mcp",
                "--header",
                "Authorization: Bearer bb_live_..."
            ]
        }
    }
}
  • Claude Desktop speaks stdio, so a small bridge, mcp-remote, connects it to our HTTP address. It needs Node.js installed.
  • The bridge itself is checked against this endpoint; the Claude Desktop app has not been run with it yet.

Cursor

Confirmed working

Create or open .cursor/mcp.json

Cursor
{
    "mcpServers": {
        "swap-backlinks": {
            "url": "https://swapbacklinks.com/api/mcp",
            "headers": {
                "Authorization": "Bearer bb_live_..."
            }
        }
    }
}
  • Use ~/.cursor/mcp.json instead to have the server in all projects.

ChatGPT

Untested

Add a custom connector (Settings, Connectors; turn on developer mode if you do not see it).

ChatGPT
Name:            Swap Backlinks
MCP server URL:  https://swapbacklinks.com/api/mcp?api_key=bb_live_...
Authentication: No authentication
  • ChatGPT's connector form has no field for a header, so the key goes in the address. Treat the whole address as a password: anyone who has it can act as you.
  • If it ever leaks, create a new key on your dashboard; the old one stops working at once. Menu names in ChatGPT change from time to time.

Codex CLI

Untested

Add to ~/.codex/config.toml

Codex CLI
# export SWAP_BACKLINKS_TOKEN=bb_live_...
 
[mcp_servers.swap-backlinks]
url = "https://swapbacklinks.com/api/mcp"
bearer_token_env_var = "SWAP_BACKLINKS_TOKEN"
  • The token comes from the environment, so the key never has to live in the config file.
  • Remote streamable-HTTP servers are set up by editing this file directly. If the server does not appear, check your Codex version's MCP documentation for any switch it needs before loading remote servers.

Gemini CLI

Untested

Add to ~/.gemini/settings.json

Gemini CLI
{
    "mcpServers": {
        "swap-backlinks": {
            "httpUrl": "https://swapbacklinks.com/api/mcp",
            "headers": {
                "Authorization": "Bearer bb_live_..."
            }
        }
    }
}
  • The Gemini CLI MCP docs use httpUrl, not url, for streamable HTTP.
  • Use .gemini/settings.json inside a repository to limit the server to that project.

Authentication

The server accepts anonymous connections and checks credentials per tool. That way connecting always succeeds and you can scout before joining.

  • Open reads. get_rules, get_categories and search_partners need nothing (apart from the Pro-only filters of the last one).
  • Keyed writes. Everything about your own sites, pairings or links reads the Authorization: Bearer bb_live_... header, and replies with the command to add one if it is missing.
  • Key in the address (ChatGPT). A client that cannot send a header may call /api/mcp?api_key=bb_live_... instead. Only this endpoint accepts it, and it is never echoed back. The address is then a secret and can show up in the logs of anything between you and us, so prefer a header whenever the client allows one.
  • Keys look like bb_live_ plus 24 random bytes in base64url, safe to paste into a shell.
  • Only a SHA-256 hash is stored. You see the key once, at creation; to replace a lost one, create a new key, which kills the old.
  • An invalid or revoked key gets 401, not a silent downgrade to anonymous.
  • Unsubscribing from email disables the key until you resubscribe (reads still work, writes explain why they refuse), pauses your sites and blocks new listings. Nothing is deleted; resubscribing reverses it.

Tool reference

Three open tools and eleven for your own account. Output is plain text written for a model, and every write tool ends with a next step so agents can keep going unprompted.

Reads without a key

get_rules

No key

Prints the four ground rules and a one-line summary of the service.

Arguments

None.

Returns
Plain text.

get_categories

No key

Lists pairing categories with site counts and median DR, in three buckets: open (8 or more sites), filling (borrows from neighbours) and empty.

Arguments

None.

Returns
Three lists. An empty category means the first person in gets paired with everyone who follows.

search_partners

No key

Searches active sites that want a swap. Results are anonymous: topic, DR, wanted anchors, offer, links given and received.

Arguments
  • categoryone of 38optional

    Omit to search everything. Exact names come from get_categories.

  • dr_mininteger 0-100optional

    Pro only.

  • dr_maxinteger 0-100optional

    Pro only.

  • offersblog_post | resources_page | existing_article | unsureoptional

    Pro only. Filters by the placement a site is willing to give.

  • limitinteger 1-20, default 5optional

    Hard cap of 20; no paging.

Returns
A block per site with an opaque id. A Pro-only argument sent without a Pro key yields an error pointing at the Plan page.

Writes with a key

Each of these needs the bearer header.

submit_site

Key required

Creates a listing. Two calls by design: the first returns a draft and stores nothing; the second, with confirm=true, saves it.

Arguments
  • urlstringrequired

    A bare domain is accepted.

  • confirmboolean, default falseoptional

    Pass true only once someone has read the draft, since partners will see it.

  • categoryone of 38optional

    Replaces the draft category. "Other" cannot be paired and is refused.

  • descriptionstring, max 2000optional

    Replaces the draft text. Leave your brand out.

  • keywordsstring[], max 25optional

    Replaces the draft anchors; normalised on the server, brand-bearing ones removed, at least one required.

  • placement_offeredblog_post | resources_page | existing_article | unsureoptional

    What you will give in return; default unsure.

Returns
Draft: domain, category, DR, anchors, text. Confirmed: the site enters review (admin-owned sites skip it). When your plan's site limit is full the call fails before any page is fetched.

list_my_sites

Key required

Your sites, newest first.

Arguments

None.

Returns
Id, domain, category, DR, status (pending_review, active, paused, rejected, banned), decline reason, minimum partner DR, links given and received.

set_matching_preferences

Key required

Per-site minimum partner DR, plus whether to skip sites with no rating. Affects future pairings in both directions.

Arguments
  • site_idstringrequired

    From list_my_sites.

  • min_partner_drinteger 0-100required

    0 disables the minimum.

  • skip_unratedboolean, default falseoptional

    An unrated site has no number to compare, so only this switch can exclude it.

Returns
Saved values, how many active sites meet the minimum, and how many of those would accept you back (the number that matters).

list_matches

Key required

Pairings for all your sites. The partner is anonymous until both accept.

Arguments
  • stateproposed | a_accepted | b_accepted | agreed | placed | declined | expiredoptional

    Omit for all. Latest 50.

Returns
Id, state, partner (masked or revealed), description, wanted anchors, each side's placement status, next step.

respond_to_match

Key required

Says yes or no to a pairing. A second yes reveals both parties to each other at once. A no on an agreed pairing withdraws you, allowed until one link is live.

Arguments
  • match_idstringrequired

    From list_matches.

  • acceptbooleanrequired

    false declines, or withdraws if already agreed.

  • reasonstring, max 500optional

    Shown to the partner when withdrawing.

Returns
New state; on mutual yes, partner details and a pointer to get_link_brief. Repeating an accept is harmless.

report_partner

Key required

Reports the partner in an agreed pairing. An admin decides; nothing changes for them before that, and they are not told who reported.

Arguments
  • match_idstringrequired

    agreed or placed.

  • reasonlink_removed | fake_site | spam | abuse | otherrequired

    What went wrong.

  • detailsstringoptional

    Up to 1000 characters; required for other.

Returns
A confirmation.

list_messages

Key required

Conversation of an agreed pairing, oldest first, up to 200 messages. Marks it read. Read-only after the exchange ends.

Arguments
  • match_idstringrequired

    An agreed pairing of yours.

  • sinceISO timestampoptional

    Only newer messages; use the latest timestamp you hold to poll.

Returns
Time, sender (You or the partner's domain) and text per line.

send_message

Key required

Writes to your partner in an agreed pairing, e.g. to fix the page and anchor.

Arguments
  • match_idstringrequired

    agreed or placed.

  • bodystring, 1-4000required

    Plain text; URLs become links on the site.

Returns
Send time. Notification emails are rate-limited and skipped while the thread is open.

get_my_standing

Key required

Summarises links given against received.

Arguments

None.

Returns
Sites, links given, links received, a standing (new, healthy, watch, behind) and one explanatory line. The first two swaps are exempt, and for now it does not influence pairing.

A swap, start to finish

Ten steps cover the whole product. You would normally just ask your agent for a swap; these are the calls it makes.

  1. 01

    Scout

    No key required. Anonymous calls can browse the catalogue and the masked pool, which tells you whether anyone like you is around.

    tool call
    search_partners({ category: "Developer Tools", limit: 5 })

    returns Masked rows: category, DR, text, anchors.

  2. 02

    Draft

    Nothing is saved. We load your homepage, write a first draft with brand and domain removed (a language model if configured, otherwise a fallback; the model never sees your domain) and look up your DR. Show the draft to the owner.

    tool call
    submit_site({ url: "https://launchzone.co" })

    returns Draft listing and a prompt to confirm.

  3. 03

    Confirm

    Now it is saved and queued for review. Once a person approves it, pairing runs immediately, so a waiting partner may already be there.

    tool call
    submit_site({
        url: "https://launchzone.co",
        confirm: true,
        keywords: ["deploy status dashboard", "rollback log"],
        placement_offered: "existing_article",
    })

    returns Submitted and pending review; list_my_sites shows when it is active.

  4. 04

    Look at the pairing

    The partner is anonymous, so you decide on niche, DR range, topic and anchors alone.

    tool call
    list_matches({ state: "proposed" })

    returns Pairing id, anonymous partner, next step.

  5. 05

    Say yes

    The second yes releases both identities simultaneously. It cannot be undone, so only accept if you will place a link.

    tool call
    respond_to_match({ match_id: "...", accept: true })

    returns Agreed, with the partner's details.

  6. 06

    Talk it through (optional)

    Settle page, anchor and timing in the pairing's thread. Your partner is not emailed for every line.

    tool call
    send_message({ match_id: "...", body: "I can link you from our deploy checklist. Would 'status page for indie makers' suit as anchor?" })
    list_messages({ match_id: "..." })

    returns Send time and the thread.

  7. 07

    Get the snippet

    Request the syntax your repo uses so your agent can paste it directly.

    tool call
    get_link_brief({ match_id: "...", format: "mdx" })

    returns URL, anchors, partner summary, snippet.

  8. 08

    Edit your site

    This step lives outside the API: your agent opens the right page, writes a sentence around the link, commits and deploys.

    in your repo
    # the agent opens content/blog/deploy-checklist.mdx, adds the link, deploys
  9. 09

    Report it

    The page is fetched at once and classified. Footer and nofollow links count, and your partner sees them exactly as they are.

    tool call
    mark_link_placed({
        match_id: "...",
        page_url: "https://launchzone.co/blog/deploy-checklist",
        anchor_used: "status page for indie makers",
    })

    returns Live, position content, followed. Rechecked after 7 days, 30 days, then monthly.

  10. 10

    Review the ledger

    Shows both directions, so you see what you got as well as what you gave.

    tool call
    check_links()

What the API can and cannot see

Anonymity is a promise, so this is what it means in API terms.

  • Nothing identifying before agreement. search_partners and list_matches never return a domain, URL or email at any limit or filter; only a masked top-level domain.
  • Mutual and simultaneous. The second accept sets the state to agreed and both sides see each other\'s details together. Nobody peeks first.
  • Opaque ids. partnerId is random and no tool takes it as input; it only distinguishes results.
  • Scrubbed text. Drafts are generated without your domain, URL or brand, then filtered for brand words; anchors that keep one are dropped. It is best effort, so read the draft first.
  • Your data is unmasked to you. list_my_sites, check_links and get_my_standing show real domains and pages.

Remember: descriptions, anchors and DR are readable by anyone, and a reveal is permanent. More on the privacy page.

Limits and good behaviour

Open reads are the easiest thing to abuse, so limits are strict.

  • Hourly budgets per tool, kept in the database, shared by web and MCP, counted per member or per IP: search_partners 60, get_categories and get_rules 120, submit_site 20, mark_link_placed 60, send_message 120, list_messages 1800, everything else 240. Over budget returns a tool error with a wait time (HTTP 429 on the web).
  • search_partners clamps limit to 1-20 and has no offset or cursor, so the pool cannot be enumerated.
  • Longest-waiting sites come first, so repeated queries return similar rows. Narrow by category (and DR on Pro) instead.
  • list_matches returns 50 pairings at most, check_links 100 links.
  • Free accounts list 2 sites, Pro up to 10; a domain has one owner.
  • A pairing nobody answers lapses after 14 days.
  • submit_site fetches pages and DR: call the draft once, not in a loop.

Harvesting members, de-anonymising profiles or looping reads can get a key revoked regardless of budget.

Errors

Errors are normal tool results with the error flag set and an instruction in the body, so an agent can recover by itself. Common ones:

  • Not signed in. A keyed tool was called without a key; the message includes the command to add one.
  • Site unreachable. The page failed to load, was not HTML, was too big or blocked our crawler. Parked or nearly empty sites are declined under ground rule §01.
  • Already listed. Another member owns that domain.
  • Not agreed. get_link_brief, mark_link_placed, list_messages or send_message needs mutual acceptance first.
  • Read-only thread. The exchange ended; you can read but not write.
  • Recorded, unconfirmed. Not an error or an accusation: we only read server HTML, so JavaScript-inserted links are invisible. We retry automatically.

Other failures are plain text. Tools sit on the same services as the web routes, so nothing is left half-written.

DR is Ahrefs Domain Rating. Ground rules: home page or get_rules. Everything else: terms and privacy.