Endpoint Reference
All paths are relative to the Partner API base URL. The scope listed for each endpoint is the minimum effective scope.
Every response body includes correlation_id. Every error uses the shared error envelope described in Errors and Limits.
Async run endpoints accept webhook_url for forward compatibility, but v0 does not deliver webhook callbacks. Poll GET /runs/:run_id for terminal status.
When GET /auth returns event_id, the key is restricted to that assigned event. Event-shaped
requests for another event return 404 EVENT_NOT_FOUND. Org-wide writes that are unnecessary for
the assigned-event workflow return 403 EVENT_SCOPE_FORBIDDEN; event, run, review, and ID-map
lists are narrowed to the assigned event as described below.
Service Account
Section titled “Service Account”GET /auth
Section titled “GET /auth”Scope: partner:read
Returns authenticated service-account details.
Response 200:
{ "org_id": "<ORG_ID>", "principal_type": "service_account", "service_account_user_id": "<SERVICE_ACCOUNT_USER_ID>", "scopes": ["partner:read", "partner:write"], "environment": "production", "event_id": "summit-2026", "capabilities": { "events_read": true, "attendees_import": true, "attendees_delete": true, "attendees_side_retag": true, "people_search": true }, "correlation_id": "<CORRELATION_ID>"}Route-specific errors: auth and scope errors only.
GET /capabilities
Section titled “GET /capabilities”Scope: partner:read
Returns { org_id, scopes, environment, event_id?, capabilities, correlation_id }. event_id is
present for an assigned-event key. Capability booleans describe enabled route families; they do
not override the assigned event boundary.
ID Resolution
Section titled “ID Resolution”GET /id-map/:kind/:partner_id
Section titled “GET /id-map/:kind/:partner_id”Scope: partner:read
Confirms whether a partner ID is mapped in your org.
Path parameters:
| Name | Description |
|---|---|
kind | Partner ID kind, such as event, session, or attendee. |
partner_id | Partner-owned ID. |
Response 200:
{ "kind": "attendee", "partner_id": "att-1001", "mapped": true, "correlation_id": "<CORRELATION_ID>"}Errors: 400 INVALID_PARTNER_KIND, 400 INVALID_PARTNER_ID, 404 PARTNER_ID_NOT_FOUND.
Events
Section titled “Events”POST /events
Section titled “POST /events”Scope: partner:write
Creates an event. You own event_id.
Request:
{ "event_id": "summit-2026", "title": "Annual Partner Summit 2026", "description": "Two-day leadership summit", "start_date": "2026-09-10", "end_date": "2026-09-11", "timezone": "America/New_York", "visibility": "private", "url_slug": "summit-2026"}Field limits:
| Field | Limit |
|---|---|
title | Required, max 200 characters. |
description | Optional, max 4000 characters. |
start_date, end_date | Optional ISO date strings. |
timezone | Optional, max 128 characters. |
visibility | Optional, max 32 characters. |
url_slug | Optional, max 128 characters. |
Response 201: event object with event_id, details, timestamps, and correlation_id.
Errors: 400 INVALID_JSON, 400 INVALID_PARTNER_ID, 400 INVALID_TITLE, 400 INVALID_DESCRIPTION, 400 INVALID_START_DATE, 400 INVALID_END_DATE, 400 INVALID_TIMEZONE, 400 INVALID_VISIBILITY, 400 INVALID_URL_SLUG, 409 PARTNER_ID_CONFLICT.
GET /events
Section titled “GET /events”Scope: partner:read
Lists events ordered descending by created_at.
Query parameters: limit (1-100, default 50), cursor.
Response 200:
{ "items": [ { "event_id": "summit-2026", "title": "Annual Partner Summit 2026", "description": "Two-day leadership summit", "event_details": null, "objectives": null, "welcome_instructions": null, "start_date": "2026-09-10", "end_date": "2026-09-11", "timezone": "America/New_York", "visibility": "private", "url_slug": "summit-2026", "created_at": "<ISO_DATETIME>", "updated_at": "<ISO_DATETIME>" } ], "next_cursor": null, "correlation_id": "<CORRELATION_ID>"}Errors: org, auth, scope, and rate-limit errors.
GET /events/:event_id
Section titled “GET /events/:event_id”Scope: partner:read
Returns one event.
Response 200: event object with context fields, timestamps, and correlation_id.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND.
PATCH /events/:event_id
Section titled “PATCH /events/:event_id”Scope: partner:write
Updates one or more event metadata fields: title, description, dates, timezone, visibility,
URL slug, event details, objectives, or welcome instructions. Response 200 is the updated
event. Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_TITLE,
400 INVALID_EVENT_UPDATE, 400 INVALID_START_DATE, 400 INVALID_END_DATE,
400 UNSUPPORTED_QUERY_PARAMETER, 404 EVENT_NOT_FOUND, and 409 URL_SLUG_CONFLICT.
DELETE /events/:event_id
Section titled “DELETE /events/:event_id”Scope: partner:write
Soft-deletes an event. Use dry_run=true to preview affected relationships; force=true is
required when protected relationships remain. Response 200 includes affected counts and
deletion/preview state. Errors: 400 INVALID_PARTNER_ID,
400 UNSUPPORTED_QUERY_PARAMETER, 404 EVENT_NOT_FOUND, and 409 EVENT_NOT_EMPTY.
POST /events/:event_id/context
Section titled “POST /events/:event_id/context”Scope: partner:write
Updates event context. At least one field is required.
Request:
{ "event_details": "Senior product and engineering leaders.", "objectives": "Maximize useful introductions.", "welcome_instructions": "Share your top goal for the event."}Each field is optional and capped at 4000 characters.
Response 200:
{ "event_id": "summit-2026", "context": { "event_details": "Senior product and engineering leaders.", "objectives": "Maximize useful introductions.", "welcome_instructions": "Share your top goal for the event." }, "correlation_id": "<CORRELATION_ID>"}Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_CONTEXT, 404 EVENT_NOT_FOUND.
GET /events/:event_id/context
Section titled “GET /events/:event_id/context”Scope: partner:read
Returns event context.
Response 200: { event_id, context, correlation_id }.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND.
Sessions
Section titled “Sessions”POST /events/:event_id/sessions
Section titled “POST /events/:event_id/sessions”Scope: partner:write
Creates a session within an event. You own session_id.
Request:
{ "session_id": "opening-keynote", "title": "Opening Keynote", "description": "Kickoff session", "start_timestamp": "2026-09-10T13:00:00Z", "end_timestamp": "2026-09-10T14:00:00Z"}Response 201: session object with session_id, event_id, metadata, timestamps, and correlation_id.
Errors: 400 INVALID_PARTNER_ID, 400 INVALID_TITLE, 400 INVALID_DESCRIPTION, 400 INVALID_START_TIMESTAMP, 400 INVALID_END_TIMESTAMP, 404 EVENT_NOT_FOUND, 409 PARTNER_ID_CONFLICT.
GET /events/:event_id/sessions
Section titled “GET /events/:event_id/sessions”Scope: partner:read
Lists event sessions ordered ascending by start_timestamp. Optional filters apply before pagination; keep the same filters when following a next_cursor.
Query parameters: limit, cursor, q, starts_after, starts_before.
| Parameter | Description |
|---|---|
q | Optional case-insensitive keyword filter over title and description. |
starts_after | Optional inclusive lower bound for start_timestamp. |
starts_before | Optional inclusive upper bound for start_timestamp. |
Response 200: { items, next_cursor, correlation_id }.
Errors: 400 INVALID_PARTNER_ID, 400 INVALID_SESSION_QUERY, 400 INVALID_STARTS_AFTER, 400 INVALID_STARTS_BEFORE, 404 EVENT_NOT_FOUND.
GET /events/:event_id/sessions/:session_id
Section titled “GET /events/:event_id/sessions/:session_id”Scope: partner:read
Returns one session.
Response 200: session object with correlation_id.
Errors: 400 INVALID_PARTNER_ID, 400 UNSUPPORTED_QUERY_PARAMETER, and
404 SESSION_NOT_FOUND.
PATCH /events/:event_id/sessions/:session_id
Section titled “PATCH /events/:event_id/sessions/:session_id”Scope: partner:write
Updates session title, description, start/end timestamps, or metadata. Response 200 is the
updated session. Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_TITLE,
400 INVALID_DESCRIPTION, 400 INVALID_START_TIMESTAMP, 400 INVALID_END_TIMESTAMP,
400 INVALID_METADATA, 400 INVALID_SESSION_UPDATE, 400 UNSUPPORTED_QUERY_PARAMETER, and
404 SESSION_NOT_FOUND.
DELETE /events/:event_id/sessions/:session_id
Section titled “DELETE /events/:event_id/sessions/:session_id”Scope: partner:write
Soft-deletes a session. Use dry_run=true to preview. The response reports the current
session-participant count but does not delete those participant rows. Response 200 includes
affected counts. Errors: 400 INVALID_PARTNER_ID, 400 UNSUPPORTED_QUERY_PARAMETER, and
404 SESSION_NOT_FOUND.
Attendees
Section titled “Attendees”GET /events/:event_id/attendees
Section titled “GET /events/:event_id/attendees”Scope: partner:read
Lists every event attendee, including people added internally before a partner attendee ID existed. Rows with partner IDs come first, followed by internally added rows ordered by Meshi entity ID. Keep the returned cursor opaque.
Query parameters: limit, cursor.
Response 200:
{ "items": [ { "attendee_id": "att-1001", "entity_id": "11111111-1111-4111-8111-111111111111", "id_source": "partner", "event_id": "summit-2026", "display_name": "Jordan Lee", "title": "VP Product", "company": "Acme", "event": { "role_at_event": null, "side": "founder", "visibility": null, "goal": null, "needs": null, "offers": null, "onboarded_at": null, "joined_at": null }, "identity": { "review_required": false, "open_review_count": 0 } } ], "next_cursor": null, "correlation_id": "<CORRELATION_ID>"}attendee_id is null and id_source is "meshi" for an internally added attendee with no
partner mapping. Use its entity_id in match exclusions. A mapped attendee also includes
entity_id, but partner integrations should keep using their stable attendee_id wherever an
endpoint path asks for one.
event.side is the matching-side tag at a two-sided event: "founder", "investor", or null.
event.role_at_event is descriptive only.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND.
GET /events/:event_id/attendees/:attendee_id
Section titled “GET /events/:event_id/attendees/:attendee_id”Scope: partner:read
Returns partner-safe enriched attendee detail. Optional comma-separated include values select
profile, event, identity, brief, traits, goals, or readiness. This path requires a
partner attendee_id; the event-roster UUID exception does not apply. Errors include
400 INVALID_INCLUDE, 404 EVENT_NOT_FOUND, and 404 ATTENDEE_NOT_FOUND.
PATCH /events/:event_id/attendees/:attendee_id
Section titled “PATCH /events/:event_id/attendees/:attendee_id”Scope: partner:write
Corrects an attendee’s matching side without changing their event role. The path accepts either
a partner attendee ID or an entity_id returned by the event roster. The update
removes that attendee’s directional event scores and queues a durable rescore fanout.
A cleared side remains intentionally unmatchable; Meshi never infers a replacement.
Request:
{ "side": "founder"}side must be "founder", "investor", or null. This side-correction body accepts no other
fields. To change event intent, re-import the stable attendee_id with the updated intent and a
fresh idempotency key after any prior run is terminal.
Response 200: { event_id, attendee_id, entity_id, id_source, side, correlation_id }.
Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_ATTENDEE_SIDE,
400 INVALID_ATTENDEE_UPDATE, 404 EVENT_NOT_FOUND, 404 ATTENDEE_NOT_FOUND.
PUT /events/:event_id/attendees:import
Section titled “PUT /events/:event_id/attendees:import”Scope: partner:write
Imports attendees into an event. Async; returns a run.
Request:
{ "idempotency_key": "<IDEMPOTENCY_KEY>", "webhook_url": "https://example.com/future-webhook", "attendees": [ { "attendee_id": "att-1001", "name": "Jordan Lee", "email": "jordan@example.com", "linkedin_url": "https://www.linkedin.com/in/jordanlee", "title": "VP Product", "company": "Acme", "side": "founder", "event_goal": "Raise our seed round", "event_needs": ["intros to seed investors"], "event_offers": ["developer-tools product with strong growth"], "raw_data": { "source": "crm-export" } } ]}Per-attendee limits:
| Field | Limit |
|---|---|
name | Required, max 240 characters. |
email | Optional, max 320 characters. |
linkedin_url | Optional, max 2048 characters. |
title | Optional, max 512 characters. |
company | Optional, max 512 characters. |
side | Optional. "founder", "investor", or null. Tags the attendee’s matching side at two-sided events; an absent or null side leaves the attendee unmatchable at a sided event (fail-closed) until tagged. See the two-sided matching guide. |
event_goal | Optional, max 4000 characters. Plain-language event context. Meshi derives useful needs and evidence-backed offers when possible. Without it, Meshi falls back to usable inferred or confirmed profile intent; readiness reports a blocker if neither source exists. |
event_needs | Optional array of strings. Explicit event-specific needs override inferred needs for this axis. Usually omit this unless the attendee supplied it directly. |
event_offers | Optional array of strings. Explicit event-specific offers override inferred offers for this axis. Usually omit this unless the attendee supplied them directly; Meshi will not invent an offer from a goal without supporting profile evidence. |
raw_data | Optional JSON object. |
Send stable identity fields such as email and LinkedIn URL whenever available; they improve
identity resolution and inferred intent. Every accepted import reapplies side; omitting it or
sending null deliberately untags that attendee. Supplied event-intent axes update the
corresponding values, while omitted axes preserve their existing values. Use
PATCH /events/:event_id/attendees/:attendee_id for a one-off side correction without starting
another import run.
The attendees array must be non-empty, contain unique attendee_id values, and contain at most 50,000 rows by default.
webhook_url is stored on the run for future compatibility, but v0 does not deliver webhooks. Poll GET /runs/:run_id for terminal status.
Response 202 first call or 200 idempotent replay: run object with kind: "import".
Initial accepted response (timestamps and metadata omitted here for readability):
{ "run_id": "<RUN_UUID>", "kind": "import", "status": "queued", "event_id": "summit-2026", "idempotency_key": "<THE_KEY_FROM_YOUR_REQUEST>", "progress": { "total": 1, "processed": 0, "created": 0, "reused": 0, "mapped": 0, "event_added": 0, "already_in_event": 0, "review_required": 0, "skipped": 0, "error_count": 0, "warnings": [] }, "error": null, "idempotent_replay": false, "correlation_id": "<CORRELATION_ID>"}Terminal import progress includes counters, warnings, and optional per-row errors. Each error
contains zero-based row, attendee_id, and message; a held identity row also contains an
actionable review_id. Resolve it through the review queue, then recheck the roster/readiness.
The terminal run remains partial. Reuse the original idempotency key only for an identical
delivery retry; use a new key for corrected work after a terminal partial or failed run.
Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_IDEMPOTENCY_KEY, 400 INVALID_ATTENDEES, 400 INVALID_ATTENDEE, 400 DUPLICATE_ATTENDEE_ID, 400 INVALID_ATTENDEE_NAME, 400 INVALID_ATTENDEE_EMAIL, 400 INVALID_ATTENDEE_LINKEDIN_URL, 400 INVALID_ATTENDEE_TITLE, 400 INVALID_ATTENDEE_COMPANY, 400 INVALID_ATTENDEE_SIDE, 400 INVALID_ATTENDEE_EVENT_GOAL, 400 INVALID_ATTENDEE_EVENT_NEEDS, 400 INVALID_ATTENDEE_EVENT_OFFERS, 400 INVALID_ATTENDEE_RAW_DATA, 400 INVALID_WEBHOOK_URL, 404 EVENT_NOT_FOUND, 409 IDEMPOTENCY_KEY_CONFLICT, 413 PAYLOAD_TOO_LARGE.
DELETE /events/:event_id/attendees/:attendee_id
Section titled “DELETE /events/:event_id/attendees/:attendee_id”Scope: partner:write
Removes an attendee from an event and from that event’s sessions. This is reversible relationship cleanup, not person erasure: the org-level person record and partner ID mapping are retained for idempotent re-import and auditability.
Full privacy erasure is not self-service in v0. Coordinate it with the Meshi implementation
contact assigned during handoff, including the attendee ID and correlation_id.
Query parameters: dry_run=true to preview counts without mutation.
deleted_at is present only when the request removed at least one event or session relationship. Retry-safe no-ops return already_absent: true without deleted_at.
Response 200:
{ "event_id": "summit-2026", "attendee_id": "att-1001", "deleted_at": "<ISO_DATETIME>", "already_absent": false, "retained_person": true, "affected": { "event_attendees": 1, "session_participants": 2 }, "correlation_id": "<CORRELATION_ID>"}Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND, 404 ATTENDEE_NOT_FOUND.
GET /events/:event_id/attendees/:attendee_id/identity
Section titled “GET /events/:event_id/attendees/:attendee_id/identity”Scope: partner:read
Returns identity decision history for one attendee, ordered descending by created_at.
Query parameters: limit, cursor.
Response 200: { event_id, attendee_id, items, next_cursor, correlation_id }.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND, 404 ATTENDEE_NOT_FOUND.
People Search
Section titled “People Search”POST /events/:event_id/search/people
Section titled “POST /events/:event_id/search/people”Scope: partner:write
Searches only people in one event. Request { query, mode?, type?, limit? }; response
{ event_id, query, mode, type, items, correlation_id }. Errors include 400 INVALID_QUERY,
400 INVALID_SEARCH_MODE, 400 INVALID_SEARCH_TYPE, 404 EVENT_NOT_FOUND, and
503 SEARCH_NOT_CONFIGURED.
POST /search/people
Section titled “POST /search/people”Scope: partner:write
Searches people across the authenticated org with the same body and response shape, excluding
event_id. Assigned-event keys cannot call this org-wide route and receive
403 EVENT_SCOPE_FORBIDDEN.
Readiness
Section titled “Readiness”GET /events/:event_id/attendees/:attendee_id/readiness
Section titled “GET /events/:event_id/attendees/:attendee_id/readiness”Scope: partner:read
Diagnostics for whether one attendee is ready for search and matching.
Response 200: { event_id, attendee_id, readiness: { match_ready, global_match_ready, event_match_ready, missing, recommended_actions }, correlation_id }.
missing names the specific prerequisites still absent (for example a brief embedding or event goal embeddings); recommended_actions says what to do about each.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND, 404 ATTENDEE_NOT_FOUND.
GET /events/:event_id/readiness
Section titled “GET /events/:event_id/readiness”Scope: partner:read
Paged readiness diagnostics for all event attendees, plus side-tag coverage for two-sided events.
Query parameters: limit, cursor.
Response 200: { items: [{ event_id, attendee_id, entity_id, id_source, readiness }], side_coverage: { policy_active, founder_count, investor_count, untagged_count, untagged_attendee_ids, untagged_entity_ids }, intent_processing: { pending_count, pending_entity_ids, failed_count, failed_entity_ids }, match_readiness: { ready_count, not_ready_count }, warnings: [{ code, severity, message, recommended_action }], next_cursor, correlation_id }.
side_coverage reports full-roster counts and lists every attendee with no valid side tag. The partner-ID array contains mapped
attendees; untagged_entity_ids also contains internally added attendees. At a two-sided event
those attendees receive no matches until tagged — check this before opening match browsing. See
the two-sided matching guide.
For an intended two-sided event, require side_coverage.policy_active: true. Top-level warnings
promote missing sides, untagged people, severe side imbalance, and event-wide match-readiness gaps.
Treat severity: "blocking" as a stop sign. match_readiness counts the full roster regardless of
pagination; each paged item explains its own missing profile or intent inputs.
intent_processing is a freshness signal, not a match-run lock. Pending or failed machine-derived
axes fall back to selected profile intent, while explicit event axes remain usable. Wait for
pending_count: 0 only when you want the newest derived intent reflected in the next snapshot.
Per-attendee readiness.missing uses event_intent_processing while work is pending and
event_intent_failed after a terminal failure.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND.
Enrichment
Section titled “Enrichment”POST /events/:event_id/enrichment-runs
Section titled “POST /events/:event_id/enrichment-runs”Scope: partner:write
Triggers attendee enrichment for an event. Async; returns a run.
Request:
{ "idempotency_key": "<IDEMPOTENCY_KEY>", "webhook_url": "https://example.com/future-webhook"}Response 202 first call or 200 idempotent replay: run object with kind: "enrichment" and progress counters.
Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_IDEMPOTENCY_KEY, 400 INVALID_WEBHOOK_URL, 404 EVENT_NOT_FOUND, 409 IDEMPOTENCY_KEY_CONFLICT.
Matching
Section titled “Matching”POST /events/:event_id/match-runs
Section titled “POST /events/:event_id/match-runs”Scope: partner:write
Triggers an attendee matching run. Async; returns a run.
Request:
{ "idempotency_key": "<IDEMPOTENCY_KEY>", "webhook_url": "https://example.com/future-webhook", "top_n": 10}top_n is optional, defaults to 10, and must be 1-50.
Response 202 first call or 200 idempotent replay: run object with kind: "match".
Errors: 400 INVALID_PARTNER_ID, 400 INVALID_JSON, 400 INVALID_IDEMPOTENCY_KEY, 400 INVALID_WEBHOOK_URL, 404 EVENT_NOT_FOUND, 409 IDEMPOTENCY_KEY_CONFLICT.
Match-run creation remains available while derived event intent is processing. The run uses explicit event axes plus profile-intent fallback; a later run picks up completed derivations.
Match results are pinned snapshots of a match run — identical queries against the same run never contradict each other. After roster changes, side re-tags, or goal edits, trigger a new match run to refresh results.
GET /events/:event_id/attendees/:attendee_id/matches
Section titled “GET /events/:event_id/attendees/:attendee_id/matches”Scope: partner:read
Lists top matches for one attendee, ordered descending by final_score.
attendee_id may be the partner attendee ID or the event-roster entity_id returned by
GET /events/:event_id/attendees. This lets an organizer read matches for internally added
attendees that do not have a partner mapping.
Query parameters: limit, cursor, optional run_id, repeatable exclude_entity_ids[]. If run_id is omitted, the latest terminal run is used.
exclude_entity_ids[] omits specific attendees from the results (for example, people the
requester has already met or plans to meet). It accepts either partner attendee IDs or the
entity_id returned by the event attendee list, applies before pagination (pages stay
full-length and cursors stay stable), and is capped at 500 values; exceeding the cap returns
400 TOO_MANY_EXCLUDE_ENTITY_IDS. A Meshi entity ID is accepted only when that person belongs
to this event. Unknown or out-of-event IDs are ignored without revealing whether they exist.
Response 200: { event_id, attendee_id, entity_id, id_source, run_id, items, next_cursor, correlation_id }.
Every match item includes both entity IDs and an ID source for each side. For a pre-existing person
with no partner mapping, the corresponding *_attendee_id is null and *_id_source is
"meshi". Match runs include these attendees as principals and targets.
Each match item includes an explainability object:
{ "explainability": { "tier": "exceptional", "reasons": [ { "kind": "aggregate_needs_to_offers", "text": "Their needs align with your offers across their event goals." }, { "kind": "best_goal_pair", "text": "Your goal pairs strongly with one of theirs." } ] }}tier is a calibrated presentation tier (exceptional, strong, promising, explore). reasons are readable, non-numeric explanations framed around each side’s stated needs and offers — lead product presentation with the tier and reasons; the raw sub_scores remain available for compatibility. Runs recorded before explainability shipped return a tier with an empty reasons array.
Errors: 400 INVALID_PARTNER_ID, 400 TOO_MANY_EXCLUDE_ENTITY_IDS, 404 EVENT_NOT_FOUND, 404 ATTENDEE_NOT_FOUND, 404 MATCH_RUN_NOT_FOUND.
GET /events/:event_id/matches
Section titled “GET /events/:event_id/matches”Scope: partner:read
Lists matches across all attendee pairs for an event, ordered descending by final_score.
Query parameters: limit, cursor, optional run_id, optional goal, repeatable
exclude_entity_ids[] (same semantics and cap as the per-attendee endpoint). session_id is not
supported; unsupported parameters return 400 UNSUPPORTED_QUERY_PARAMETER with
supported_parameters.
Response 200: { event_id, run_id, items, next_cursor, correlation_id }.
Errors: 400 INVALID_PARTNER_ID, 400 UNSUPPORTED_QUERY_PARAMETER,
400 TOO_MANY_EXCLUDE_ENTITY_IDS, 404 EVENT_NOT_FOUND, and 404 MATCH_RUN_NOT_FOUND.
GET /events/:event_id/matches/:principal_partner_id/:target_partner_id
Section titled “GET /events/:event_id/matches/:principal_partner_id/:target_partner_id”Scope: partner:read
Returns one ordered pair match score. Each path identifier may be a partner attendee ID or an
event-roster entity_id.
Query parameters: optional run_id, repeatable exclude_entity_ids[]. If the requested target is
excluded, the response is 404 MATCH_NOT_FOUND.
Response 200:
{ "event_id": "summit-2026", "match_id": "<MATCH_ID>", "run_id": "<RUN_ID>", "principal_attendee_id": "att-1001", "principal_entity_id": "<PRINCIPAL_ENTITY_ID>", "principal_id_source": "partner", "target_attendee_id": "att-1002", "target_entity_id": "<TARGET_ENTITY_ID>", "target_id_source": "partner", "final_score": 0.87, "sub_scores": { "goal_complementarity": 0.9, "goal_needs_to_offers": 0.8, "goal_offers_to_needs": 0.7, "value_alignment": 0.6, "general_similarity": 0.5, "score_a_to_b": 0.85, "score_b_to_a": 0.82 }, "best_goal_match": { "score": 0.9, "direction": "needsToOffers" }, "driving_goal_pair": { "score": 0.9, "direction": "needsToOffers" }, "intent_strength": { "aToB": 0.6, "bToA": 0.4 }, "input_hash": "<INPUT_HASH>", "algorithm_version": "<ALGORITHM_VERSION>", "policy_version": "<POLICY_VERSION>", "context_snapshot_id": "<CONTEXT_SNAPSHOT_ID>", "freshness": { "computed_at": "<ISO_DATETIME>" }, "computed_at": "<ISO_DATETIME>", "correlation_id": "<CORRELATION_ID>"}Errors: 400 INVALID_PARTNER_ID, 400 TOO_MANY_EXCLUDE_ENTITY_IDS, 404 EVENT_NOT_FOUND,
404 ATTENDEE_NOT_FOUND, 404 MATCH_RUN_NOT_FOUND, and 404 MATCH_NOT_FOUND.
Goal IDs are not exposed. best_goal_match and driving_goal_pair surface score and direction only.
When both directions have usable goal text, a company-framed best_goal_pair explainability reason
may include that readable text without internal IDs.
POST /runs
Section titled “POST /runs”Scope: partner:write
Creates a generic run outside event scope.
Request:
{ "kind": "import", "idempotency_key": "<IDEMPOTENCY_KEY>", "progress": {}, "snapshot_metadata": {}, "version_metadata": {}, "webhook_url": "https://example.com/future-webhook"}kind must be import, enrichment, or match.
Response 201 first call or 200 idempotent replay: run object. event_id is null for non-event runs.
Errors: 400 INVALID_JSON, 400 INVALID_RUN_KIND, 400 INVALID_IDEMPOTENCY_KEY, 400 INVALID_PROGRESS, 400 INVALID_SNAPSHOT_METADATA, 400 INVALID_VERSION_METADATA, 400 INVALID_WEBHOOK_URL.
GET /runs
Section titled “GET /runs”Scope: partner:read
Lists all runs for the org, ordered descending by created_at.
Query parameters: limit, cursor, optional kind.
Response 200: { items, next_cursor, correlation_id }.
Errors: 400 INVALID_RUN_KIND.
GET /runs/:run_id
Section titled “GET /runs/:run_id”Scope: partner:read
Polls one run. This is the canonical terminal-status poll for import, enrichment, and match runs.
For kind: "import", terminal progress uses the import counters and per-row errors documented
under attendee import, including an actionable review_id for a held identity row. Other run
kinds retain their documented progress objects.
Path parameters: run_id.
Response 200:
{ "run_id": "<RUN_ID>", "kind": "match", "status": "queued", "event_id": "summit-2026", "idempotency_key": "<IDEMPOTENCY_KEY>", "progress": {}, "error": null, "snapshot_metadata": {}, "version_metadata": {}, "webhook_url": null, "created_at": "<ISO_DATETIME>", "updated_at": "<ISO_DATETIME>", "started_at": null, "terminal_at": null, "correlation_id": "<CORRELATION_ID>"}Errors: 400 INVALID_RUN_ID, 404 RUN_NOT_FOUND.
Review Queue
Section titled “Review Queue”GET /events/:event_id/review-queue
Section titled “GET /events/:event_id/review-queue”Scope: partner:read
Lists open identity reviews for one event’s attendees, ordered descending by created_at.
Query parameters: limit, cursor.
Response 200: { items: [{ review_id, kind, partner_id, display_name, candidate: { display_name, title, company, linkedin_url }, source_path, candidate_ref, confidence, reasoning, evaluator, created_at }], next_cursor, correlation_id }.
Errors: 400 INVALID_PARTNER_ID, 404 EVENT_NOT_FOUND.
GET /review-queue
Section titled “GET /review-queue”Scope: partner:read
Lists open identity reviews across the org, ordered descending by created_at.
Query parameters: limit, cursor.
Response 200: { items: [{ review_id, kind, partner_id, display_name, candidate: { display_name, title, company, linkedin_url }, source_path, candidate_ref, confidence, reasoning, evaluator, created_at }], next_cursor, correlation_id }.
candidate gives the organizer safe comparison details for the proposed existing person. It
deliberately omits email addresses and Meshi internal IDs. Accept only if the available identity
evidence supports the same person; reject a clearly different candidate, then inspect
remaining_reviews, or choose distinct when no candidate is the imported person.
Route-specific errors: auth and scope errors only.
POST /review-queue/:review_id/resolve
Section titled “POST /review-queue/:review_id/resolve”Scope: partner:write
Accepts, rejects, or marks an open review as a distinct person.
Request:
{ "decision": "accept", "reason": "Confirmed by source system operator."}decision must be accept, reject, or distinct.
Response 200: { review_id, kind, partner_id, decision, logged: true, minted?, remaining_reviews?, enrichment_requeued: { enrichment_requested, skipped }, correlation_id }.
For a held import, accept adopts the selected existing person and distinct mints a new one;
both map the original attendee ID and apply imported event data. reject rejects one candidate
and returns remaining_reviews. Rejecting the final candidate returns
409 REJECT_LAST_CANDIDATE without consuming it. Conflict-safe 409 codes
REVIEW_ALREADY_RESOLVED, ATTENDEE_ID_CONFLICT, IDENTITY_CONFLICT, and
DISTINCT_IDENTITY_CONFLICT also leave the review open; relist before retrying.
Errors: 400 INVALID_REVIEW_ID, 400 INVALID_JSON, 400 INVALID_REVIEW_DECISION,
404 REVIEW_NOT_FOUND, and the conflict-safe 409 codes above.
POST /review-queue/:review_id/merge
Section titled “POST /review-queue/:review_id/merge”Scope: partner:write
Resolves a review by merging the source entity into the target entity.
Request:
{ "source_kind": "attendee", "source_partner_id": "att-dupe", "target_kind": "attendee", "target_partner_id": "att-1001", "reason": "Same person in source system."}Response 200: { review_id, source, target, merged: true, superseded_anchors, rewritten_anchors, deleted_computations, correlation_id }.
Errors: 400 INVALID_REVIEW_ID, 400 INVALID_JSON, 400 INVALID_PARTNER_KIND, 400 INVALID_PARTNER_ID, 400 REVIEW_SOURCE_MISMATCH, 400 MERGE_REJECTED, 404 REVIEW_NOT_FOUND, 404 PARTNER_ID_NOT_FOUND.