Skip to content

Canonical Flow

This is the recommended end-to-end sequence for a partner integration.

Set the base URL and API key for the current docs hostname:

Production

export MESHI_BASE="https://api.meshi.io/api/v0/partner"
export MESHI_KEY="<MESHI_PARTNER_PRODUCTION_KEY>"

Use this for live partner traffic.

Staging

export MESHI_BASE="https://api.staging.meshi.io/api/v0/partner"
export MESHI_KEY="<MESHI_PARTNER_STAGING_KEY>"

Use this while building and validating an integration.

Terminal window
curl -sS "$MESHI_BASE/auth" \
-H "Authorization: Bearer $MESHI_KEY"

Continue only if the response includes partner:write in scopes. If it includes event_id, require that exact assigned event throughout this flow and skip event creation; event-scoped keys cannot create another event or use org-wide routes.

You own event_id. Use a stable ID from your event system.

Terminal window
curl -sS -X POST "$MESHI_BASE/events" \
-H "Authorization: Bearer $MESHI_KEY" \
-H "Content-Type: application/json" \
-d '{
"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"
}'

201 means the event was created. 409 PARTNER_ID_CONFLICT means that ID already exists in your org; fetch the event and continue if it is the same logical event.

Context helps downstream matching use event-specific goals and instructions. Send at least one field.

Terminal window
curl -sS -X POST "$MESHI_BASE/events/summit-2026/context" \
-H "Authorization: Bearer $MESHI_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_details": "Senior product and engineering leaders from portfolio companies.",
"objectives": "Maximize useful cross-company introductions.",
"welcome_instructions": "Introduce yourself with your top goal for the summit."
}'

Each context field is optional, but at least one must be present. Each value is capped at 4000 characters.

Imports are async. Each attendee needs a stable attendee_id and name; email, LinkedIn URL, title, company, and raw data are optional.

Terminal window
IDEMP=$(uuidgen)
curl -sS -X PUT "$MESHI_BASE/events/summit-2026/attendees:import" \
-H "Authorization: Bearer $MESHI_KEY" \
-H "Content-Type: application/json" \
-d "{
\"idempotency_key\": \"$IDEMP\",
\"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\": \"Meet investors and enterprise design partners\",
\"raw_data\": { \"source\": \"crm-export-2026-06\" }
},
{
\"attendee_id\": \"att-1002\",
\"name\": \"Sam Rivera\",
\"title\": \"Head of Partnerships\",
\"company\": \"Globex\",
\"side\": \"investor\",
\"event_goal\": \"Meet founders building enterprise software\"
}
]
}"

The first accepted call returns 202. Capture run_id; the initial response has this shape:

{
"run_id": "<RUN_UUID>",
"kind": "import",
"status": "queued",
"event_id": "summit-2026",
"idempotency_key": "<THE_KEY_FROM_YOUR_REQUEST>",
"progress": {
"total": 2,
"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>"
}

A replay with the same idempotency key and event returns 200 with idempotent_replay: true and the original run.

Poll GET /runs/:run_id until status is terminal: succeeded, partial, or failed.

Terminal window
poll_run () {
local run_id="$1"
local delay=2
local max=30
while :; do
resp=$(curl -sS "$MESHI_BASE/runs/$run_id" \
-H "Authorization: Bearer $MESHI_KEY")
status=$(echo "$resp" | jq -r '.status')
case "$status" in
succeeded|partial|failed)
echo "$resp"
return 0
;;
*)
sleep "$delay"
if [ "$delay" -lt "$max" ]; then delay=$((delay * 2)); fi
if [ "$delay" -gt "$max" ]; then delay="$max"; fi
;;
esac
done
}
poll_run "<IMPORT_RUN_ID>"

For partial or failed, inspect progress and error. A representative partial import looks like this:

{
"status": "partial",
"progress": {
"total": 2,
"processed": 2,
"created": 1,
"reused": 0,
"mapped": 1,
"event_added": 1,
"already_in_event": 0,
"review_required": 1,
"skipped": 0,
"error_count": 1,
"errors": [{
"row": 1,
"attendee_id": "att-1002",
"message": "Identity review required",
"review_id": "<REVIEW_UUID>"
}],
"warnings": []
},
"error": {
"code": "PARTIAL_IMPORT",
"message": "One or more attendee rows were held or rejected during import"
}
}

A held identity row includes a zero-based row, attendee_id, message, and review_id in progress.errors. List the review queue and resolve that review with accept, reject, or distinct; a reject returns remaining_reviews, while rejecting the final candidate returns 409 REJECT_LAST_CANDIDATE and leaves it open. Resolution applies the held import but does not change the terminal run from partial to succeeded, so verify the roster/readiness afterward.

Terminal window
curl -sS "$MESHI_BASE/review-queue?limit=50" \
-H "Authorization: Bearer $MESHI_KEY"
curl -sS -X POST "$MESHI_BASE/review-queue/<REVIEW_ID>/resolve" \
-H "Authorization: Bearer $MESHI_KEY" \
-H "Content-Type: application/json" \
-d '{ "decision": "accept", "reason": "Confirmed by source system operator." }'

accept adopts the selected person, distinct creates a new one, and reject rejects one candidate. Conflict-safe 409 responses leave the review open; relist before retrying.

Reuse the same idempotency key only for an identical request whose delivery is uncertain. A terminal replay returns the existing run. Use a fresh key for corrected work after a terminal partial or failed run.

Terminal window
IDEMP=$(uuidgen)
curl -sS -X POST "$MESHI_BASE/events/summit-2026/enrichment-runs" \
-H "Authorization: Bearer $MESHI_KEY" \
-H "Content-Type: application/json" \
-d "{ \"idempotency_key\": \"$IDEMP\" }"

The response includes a queued run. Poll it to terminal with the same poll_run function. Terminal enrichment means attendee-level work was queued; it does not mean every profile, embedding, or intent derivation is ready.

Example enrichment progress:

{
"total": 2,
"processed": 2,
"enrichment_requested": 2,
"review_required": 0,
"skipped": 0,
"error_count": 0,
"errors": []
}

Rows with REVIEW_REQUIRED need identity review resolution. Rows with NO_SOURCE_RECORD had no source record available for enrichment.

Recheck GET /events/summit-2026/readiness after enrichment. Fix blocking warnings, missing profile/intent inputs, and explicit side coverage before matching. For a two-sided event, require side_coverage.policy_active: true; founder_count, investor_count, and SEVERE_SIDE_IMBALANCE make roster capacity visible. intent_processing is a freshness signal: a match run can use explicit event axes plus usable inferred profile intent while derivation is pending, or you can wait for the newest derivation.

Terminal window
IDEMP=$(uuidgen)
curl -sS -X POST "$MESHI_BASE/events/summit-2026/match-runs" \
-H "Authorization: Bearer $MESHI_KEY" \
-H "Content-Type: application/json" \
-d "{ \"idempotency_key\": \"$IDEMP\", \"top_n\": 10 }"

top_n is optional, defaults to 10, and must be between 1 and 50. Poll the returned match run to terminal. Trigger a fresh run after roster, side, or intent changes.

Read all matches for the event:

Terminal window
curl -sS "$MESHI_BASE/events/summit-2026/matches?limit=50&run_id=<MATCH_RUN_ID>" \
-H "Authorization: Bearer $MESHI_KEY"

Read top matches for one attendee:

Terminal window
curl -sS "$MESHI_BASE/events/summit-2026/attendees/att-1001/matches?limit=10&run_id=<MATCH_RUN_ID>&exclude_entity_ids[]=att-1002" \
-H "Authorization: Bearer $MESHI_KEY"

Read one ordered pair:

Terminal window
curl -sS "$MESHI_BASE/events/summit-2026/matches/att-1001/att-1002" \
-H "Authorization: Bearer $MESHI_KEY"

Match lists are ordered descending by final_score. Repeated exclusions accept partner attendee IDs or event-roster entity_id handles and apply before pagination. If run_id is omitted, Meshi uses the latest terminal match run; pin it while paging. Event-wide results support goal text but not session_id.

Enrichment may surface additional identity reviews. Use the same list-and-resolve sequence from the import recovery step; valid decisions remain accept, reject, and distinct.

  • Use the production base URL and production key.
  • Log every correlation_id.
  • Generate one idempotency key per logical write operation, and reuse it only for retries of that same operation.
  • Poll async runs to terminal; do not wait for webhook delivery in v0.
  • Page through next_cursor verbatim until it is null.
  • On 429, honor the Retry-After header before retrying.