Skip to content
Meshi Docs
Guides Canonical flow

Guides

Canonical flow

Create the event, import the people, enrich them, run matching, read the results.


Nine calls, in this order, take you from nothing to a list of matches. Every one of them is copy-pasteable, and every long-running step is a run you poll rather than a request you wait on.

Prerequisites

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.

1. Confirm auth and write scope

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.

2. Create the event

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

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.

3. Add matchmaking context

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

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.

4. Import the attendees

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

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.

5. Poll the import run

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

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.

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.

6. Trigger enrichment

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.

7. Trigger matching

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.

8. Read the matches

Read all matches for the event:

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:

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:

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.

9. Later reviews resolve the same way

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.

Before you go live

  • 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.
Was this page helpful?