Peers.

Get started with Peers

Peers matches the people in your space and auto-delivers introductions to your best matches, you're always in control. Pick how you drive it, talk to it through your AI assistant, or call the REST API. Most people connect through Claude and never touch the API.

The Peers flow: human intent (someone tells their assistant what they're looking for) → Claude or ChatGPT → the Peers reasoning layer → auto-delivered intro → a real introduction.
Human intent → your AI assistant → the Peers reasoning layer → auto-delivered intro → a real introduction.

Overview

A space is any group you want to make introductions within, an event, a community, a cohort, a network. People join and share what they need and offer. Peers looks for complementarity, scores candidate pairs, and auto-introduces your best matches directly: you're always in control (switch to approve-first or opt out anytime).

Connect, two ways

Both paths reach the same Peers. Start with your assistant; drop to the API only if you're building something custom.

Through your AI assistant

Recommended
  • 1Add https://api.linkpeers.com/mcp to Claude, ChatGPT, Gemini, or Grok.
  • 2Authorize once: your assistant runs the OAuth flow. No keys to paste.
  • 3Just chat: "create a space", "import my guest list", "run matching." Done.
Transport Streamable HTTP · Auth OAuth 2.0 (PKCE)

Call the REST API

For builders
  • 1POST /v1/keys/request: get a one-time code by email.
  • 2POST /v1/keys/exchange: trade it for an mm_sk_ key.
  • 3Call /v1 with an Authorization: Bearer header. Done.
Base https://api.linkpeers.com · read-only keys are GET-only

You don't have to write code. Connect through your assistant and ask in plain language, it calls the tools for you. The tool and endpoint reference below is there when you need the detail.

The flow

However you connect, the lifecycle of a space is the same:

Step 1

Create a space

Ask your assistant to create a space and pick a slug. You become its organizer.

Step 2

Bring people in

Share the join link so people add themselves through their own assistant, or import an existing list from CSV. Importing is optional.

Step 3

Matching runs

Matching runs automatically, hourly, and whenever someone joins or updates their profile. You can also trigger a run on demand. Peers scores complementarity and stages the best pairs.

Step 4

Auto-intros go out

By default, Peers auto-introduces your best matches directly by email: you're always in control (switch to approve-first or opt out anytime).

Try it free in 3 calls

You do not need a plan, real members, or real email addresses to see what Peers would do. Ask your assistant for these three, in order:

Call 1

create_space({ name: "Trial", slug: "my-trial", sandbox: true })

A private throwaway space. It can never send email to anyone, is never billed, is never auto-matched, and nobody else can join it — so join links, QR codes and the share kit do not apply. A sandbox never becomes a live space, so give it a preview slug and keep the real name for the real room.

Call 2

import_attendees({ slug: "my-trial", sample: true })

Loads Peers' roster of fictional people, all on @example.com addresses that cannot reach a real inbox. Their profiles take about a minute to be read. Or upload your own list instead — import_attendees({ slug, records: [...] }), up to 50 people, any columns; everything beyond email, name and linkedin is read as free text, so a paragraph per person works better than a title alone. Use a real list only if you are allowed to share those people's details.

Call 3

dry_run_matching({ slug: "my-trial" })

The real engine with sending switched off: every pair it would introduce, every pair it declined and why, and the actual email each pair would have received. The response carries a reportUrl you can open in a browser. Edit the space's matchPolicy with update_space and run it again to see exactly what your change did.

Calling a tool: payload and response

Normally your assistant does this for you. If you are driving MCP directly, every call is JSON-RPC over POST /mcp with your API key in the Authorization header. Tool arguments are the parameters listed for each tool below.

# Request
POST /mcp
Authorization: Bearer mm_sk_...
Accept: application/json, text/event-stream

{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": {
    "name": "dry_run_matching",
    "arguments": { "slug": "founders-2026", "refresh": true }
  }
}

Every tool replies in the same envelope: a content array whose first entry is text. Most tools return prose written for an assistant to relay. A few return JSON inside that text so it can be reasoned over rather than read aloud, and dry_run_matching is one of them.

# Response envelope (all tools)
{
  "jsonrpc": "2.0", "id": 1,
  "result": { "content": [ { "type": "text", "text": "..." } ] }
}

# The text of a finished dry_run_matching, parsed
{
  "status": "ready",
  "policyVersion": 3,
  "counts": { "candidates": 45, "judged": 12, "accepted": 3, "rejected": 9 },
  "accepted": [
    { "a": "Rhiannon Beck", "b": "Noor Haddad", "score": 0.88,
      "aGains": "...", "bGains": "...", "nextStep": "...", "risk": "low" }
  ],
  "declined": [
    { "a": "Noor Haddad", "b": "Gustav Lindqvist", "why": "not_useful",
      "explanation": "Judge found no useful exchange: a judge/policy decision", "note": "..." }
  ],
  "changedSinceLastRun": { "added": [], "removed": [], "policyChanged": true }
}

A first call returns {"status":"started"} instead: matching is asynchronous, so wait ten to thirty seconds and call again to read the finished report. Other statuses you may get back are rate_limited, in_flight, and a plan or member-floor refusal, each with a message explaining what to do about it. Two declined categories are not policy rejections and say so in their explanation: lost_to_cap (approved, but over the space's pace) and not_judged (outranked by stronger candidates).

Mutating tools are marked Mut. Read-only API keys are rejected on those. Errors come back as a JSON-RPC error with a message written to be shown to the user.

Organizer tools

For the person who runs the space. MUT marks a tool that changes data (blocked for read-only access).

Question-generation cost: if you do not provide questions, Peers generates them once when the space is created. Owner-authored questions skip that call and stay unchanged when goals change. Switching back to generated questions requires an explicit regenerateIntakeQuestions: true, which queues one generation call.

create_spaceMutCreate a space; optionally provide judge policy and 1–8 custom text questions. Policy guides matching but cannot override member exclusions, consent, privacy, evidence, or mutual value. Pass sandbox: true for a private throwaway space that can never email anyone: it is free to dry-run on any plan and is how you preview matching before inviting real people.name, slug, type?, description?, matchingGoals?, matchPolicy?, intakeQuestions?, sandbox?, listed?
import_attendeesMutThree forms, one per call. csv: a plain email/name list; those people still have to add their own needs and offers. records: an enriched list whose columns (title, company, looking_for, offers, linkedin) become a matchable profile immediately — Pro on a real space and requires attestPermission: true; free and unattested on a sandbox (up to 50 on a sandbox). sample: true: loads Peers' fictional roster into a sandbox space so a dry run has something to judge; refused on a real space. Profiles take about a minute to be read.slug, csv?, records?, sample?, attestPermission?
list_participantsList participants with name, email, role, and memory count.slug
get_space_statsAggregate stats plus active policy/version, questions, their source, and generation status.slug
update_spaceMutUpdate goals, judge policy, questions, consent, or intro pace. Goal edits regenerate only generated questions; custom questions are preserved. Use the explicit flag to switch back to generation.slug, matchingGoals?, matchPolicy?, intakeQuestions?, regenerateIntakeQuestions?, consentDefault?, freeIntroLimit?
run_matchingMutTrigger a match run on demand. Matching also runs automatically, hourly, and on new members / profile updates.slug
set_space_boostsMutTurn the anonymized Asks Board on/off for your space, or take a specific boost down. Disabling hides the space's boosts everywhere.slug, enabled?, removeBoostId?
dry_run_matchingMutRun the real engine with delivery suppressed. Returns the pairs it would introduce, the pairs it declined with a category, and the email each pair would receive. Nothing is created and nobody is emailed. Every response — including refusals — carries reportUrl, the browser page for this space's dry runs. Re-runs automatically when the match policy version moves; pass refresh after a roster change. Status no_candidates means the run judged nothing (usually profiles were still being read): wait about a minute and call again rather than editing the policy. Free on a sandbox space, Pro on a real one; 10 runs per space per rolling 24 hours.slug, refresh?
manage_emailGet a one-time browser link to connect the organizer's own SMTP account, so broadcasts send from their address. Credentials are never entered in chat.slug
broadcast_to_spaceMutSend one message to every member through the organizer's verified SMTP. Rate-limited to one per space per 24 hours. Opted-out and departed members are skipped.slug, subject, message, ctaUrl?
setup_payoutsMutStart Stripe Connect onboarding so a paid space can pay out. Returns a hosted Stripe link; identity and bank details never touch Peers.country?
get_payout_statusWhether charges and payouts are enabled, what Stripe still requires, and whether the account is ready for paid spaces.(none)
configure_paid_spaceMutSet admission price, currency, capacity, refund policy, and intro pace for a paid space. status is draft, active, or paused.slug, status, memberCapacity, price, currency, refundPolicy, defaultIntrosPerWeek?, defaultIntrosPerMonth?

Participant tools

For everyone in a space: including the organizer, who is also a participant. A member never needs the website: their assistant can join, build a profile, browse the room, ask for an introduction and accept one, entirely through these tools.

whoamiReturn the signed-in person's identity, scope, and permission.
list_my_spacesList all spaces the person has joined.
get_spacePublic summary of a space by slug.slug
join_spaceMutJoin a public space (invite-only spaces are rejected). Returns the space's intake questions and offer options for you to read out — the member picks or rewords, never the assistant. If you were invited with a code, pass referralCode: the person who invited you gets connected to you with a warm intro (and those intros don't count against anyone's weekly/monthly caps).slug, copyFromSlug?, referralCode?
talk_to_matchmakerMutSend a message to the space matchmaker and get the reply: the default way to build your profile; it extracts needs/offers automatically.slug, message
submit_intakeMutSubmit intake tags (instant) and free-text answers (queued for extraction). Answers only work for spaces that have intake questions defined, otherwise the call redirects you to talk_to_matchmaker; tags always work.slug, tags?, answers?
list_my_memoriesList the caller's active memories in a space.slug
remove_memoryMutCorrect one of your own memories (replacement queued for extraction), or delete it by omitting text.memoryId, text?
browse_unmatchedFree discovery: who in the room you have not been paired with yet, shown anonymously — what they want, what they bring, no names or contact details. Everyone shown also lands in your inbox as a recommendation; triage from there.slug?, limit?
inbox_listYour single inbox: intros delivered to you, requests from others, recommendations worth meeting, check-ins, and platform messages, newest first. Start every session here — it is the only read that tells you what is waiting.cursor?, type?, space?, since?, limit?
inbox_requestMutAsk to be introduced to someone; asking back is how you accept — a specific reason of at least 20 characters is required and is shown to them.itemId, reason
inbox_respondMutAccept or decline a delivered introduction. Contact details are only exchanged once both sides accept.itemId, accept
inbox_dismissMutPass on any inbox item — nothing is sent to anyone. The optional reason is never shown to another member and is the most useful signal for improving matching.itemId, reason?
inbox_searchSearch your whole reach — every room you're in at once — for members whose stated needs, offers or background answer a question, in your own words. Results also land in your inbox.query, type?, space?, limit?
get_member_profileRead everything one member has said, by the opaque memberId from a browse_unmatched entry or an inbox item — still anonymous, no name or contact details.memberId
why_no_introsExplain, honestly and specifically, why you have not received introductions in a space, with one suggested next step.slug
find_matchesMutPaid deep search for a current need. With explicit confirmation, saves that need as matchable and expiring, judges a bounded shortlist, and returns strong opportunities without sending introductions — they also land in your inbox as recommendations. New people are preferred; past connections require new evidence and a new reason.slug, need, confirmMatchable, expiresInDays?, maxResults?, includePastConnections?
set_preferencesMutUpdate your consent mode, discovery breadth, and intro pace — call with only slug to read current settings back without changing anything. matchStrictness: exploratory (widest, allows specific low-risk inferred matches), balanced (default), or selective (strongest explicit value only). accountIntrosPerWeek caps total introductions across every space; introsPerWeek/introsPerMonth set the pace for this space only. maxRequestsFromOthers caps how many people per week may ask to be introduced to you. referralIntros: whether people who join with your invite code get auto-connected to you (default on; these don't count toward your caps either way). Optionally add LinkedIn/phone shared only after mutual consent.slug, consentMode?, introCap?, introsPerWeek?, accountIntrosPerWeek?, maxRequestsFromOthers?, introsPerMonth?, matchStrictness?, referralIntros?, outreachOptOut?, linkedinUrl?, phone?, sharePhone?
block_personMutNever be matched with a specific person in this space ("don't match me with X"). Resolves by display name.slug, name
unblock_personMutUndo a previous block: allow this person to be matched with you again.slug, name
check_join_statusWhere a join stands for a paid space: pending, processing, paid, failed, expired, refunded, disputed, or revoked, with the amount and paid-at time once settled.slug
profile_suggestionsFacts Peers has inferred about you in a space but not yet stored, each with a suggested confirming question. Nothing is matched on until you confirm it.slug
recommend_spacesDiscover other listed spaces whose members fit your needs and offers, ranked by fit.limit?
get_share_kitGet everything to invite others: a personal referral link, the space's value angles, and your referral count.slug
preview_boostMutTurn one of your asks into an anonymized, shareable post: drafts it but publishes nothing. Strips your name, employer, and contact details, generalizes the ask so it can't single you out, then returns a LinkedIn post, an X post, and (by default) a line for the public Asks Board, with a boostId to approve. Opt-in; sensitive topics need sensitiveAck.slug, sourceMemoryId?, sensitiveAck?, mode?
publish_boostMutPublish a boost draft you already reviewed, by its boostId: this only flips that exact draft live; it never accepts new text, so nothing you haven't seen can go out.boostId
list_my_boostsList your Ask Boost drafts and published posts (id, status, the anonymized text).slug?
unpublish_boostMutTake one of your boosts down from the Asks Board.boostId
give_feedbackMutRecord feedback about the experience or a specific intro.slug, introId?, useful?, met?, rating?, note?…
leave_spaceMutStop participating in a space: pending intros are cancelled and your profile stops being matched. Your data is kept unless you also delete it.slug
delete_my_dataMutErase your data, one space or everywhere. Immediate soft-delete, then a permanent purge after 30 days.scope (space|all), slug?
revoke_my_agent_accessMutRevoke all agent (MCP) access tokens you've granted.

REST API, authentication

Every request carries an API key in the Authorization header. Create one by requesting a one-time code by email, then exchanging it. Read-only keys are rejected on any write (non-GET) request. Paths are versioned under /v1.

# 1. Request a one-time code
POST /v1/keys/request
{ "email": "you@example.com" }

# 2. Exchange the emailed code for a key
POST /v1/keys/exchange
{ "email": "you@example.com", "code": "1234", "name": "my-app" }
→ { "apiKey": "mm_sk_…", "keyPrefix": "mm_sk_…", "permissionLevel": "read_write" }

# 3. Use it on every request
Authorization: Bearer mm_sk_…

Spaces

matchPolicy accepts objective, mustHave[], prefer[], avoid[], and organizerReview[]. Matching configuration is private to the organizer.

POST/v1/spacesCreate a space; optional matchPolicy and intakeQuestions. Returns joinUrl and configuration state.
GET/v1/spaces/:slugPublic space summary. No auth. 404 for private spaces.
GET/v1/spaces/:slug/configRead policy/version and questionnaire state (organizer only).
PATCH/v1/spaces/:slugUpdate goals, policy, or questions; use regenerateIntakeQuestions: true for one generation job (organizer only).
POST/v1/spaces/:slug/joinSelf-join a public space.
GET/v1/spaces/:spaceId/participantsRoster (organizer only).

Chat & profile

POST/v1/spaces/:slug/chatMessage the space matchmaker. Body: { message }. Returns { reply, done }.
GET/v1/spaces/:slug/me/memoriesList your own active memories in a space.
DEL/v1/memories/:idSoft-delete one of your own memories.

Import

Import a CSV directly, or presign an S3 upload for larger files.

POST/v1/spaces/:spaceId/importUpload a CSV (multipart file, ≤10MB). Enqueues an import job.
POST/v1/spaces/:spaceId/import/recordsImport enriched records as JSON (Pro; ≤500/call — free and unattested on a sandbox space). Body: { records[], attestPermission }.
POST/v1/spaces/:spaceId/import/presignGet a presigned S3 PUT URL. Body: { filename }.
POST/v1/spaces/:spaceId/import/completeMark a presigned upload complete and enqueue import. Body: { storageKey }.
GET/v1/spaces/:spaceId/import/:batchIdImport batch status, row counts, and errors.

Introductions

Consent and feedback are token-authenticated links delivered by email: no API key needed. They render a confirmation page and are idempotent.

GET/i/:introId/:side/:actionAccept or decline an intro from an email link. side a|b, action accept|decline, signed ?t=.
GET/f/:introId/:side/:verdictThumbs up/down on a delivered intro. verdict up|down, signed ?t=.

Keys & sign-in

POST/v1/keys/requestEmail a one-time code to create an API key.
POST/v1/keys/exchangeExchange the code for a new mm_sk_ key.
POST/v1/auth/magic-linkRequest a magic sign-in link by email.
POST/v1/auth/verifyVerify a magic-link token and start a session.
GET/v1/auth/meThe currently signed-in person.

MCP clients don't use API keys. The /mcp endpoint authenticates over OAuth 2.0 (PKCE), which your assistant negotiates automatically. API keys are only for direct REST calls.