Connect an AI assistant (participants)

Let a person’s AI assistant find Jade events and register them. Personal assistants connect with no account and prove the person’s email with an emailed code; partner integrations use an API account. Either way, payment stays with the person, in their browser.

Personal assistants — no account needed

For a person’s own AI assistant — Siri, ChatGPT, Claude, Grok, Muse, or one you build. Any assistant can connect: there is no account, key or OAuth sign-in. Before it can act for someone it proves their email with a code Jade sends them; from then on it works as that person, through the same services as the registration page and the registration dashboard, at the same prices and under the same rules. This is the recommended way to register people from an AI assistant.

Payment stays in the person’s browser. When money is due or a card is needed, submit_registration and submit_changes submit nothing: a client that supports URL elicitation shows the person a Jade payment link, and any other client gets payment_required with a paymentUrl to pass on. Registrations and changes with nothing to pay are submitted by the assistant.

Connecting

/api/mcp/agents

The endpoint uses the streamable-http transport, statelessly: send each JSON-RPC message as its own POST. No MCP session is kept, so a tools/call works without an initialize first; GET and DELETE answer 405. There is nothing to authenticate: send no Authorization header.

In an assistant that takes a remote MCP server by address — a custom connector in Claude or ChatGPT, for example — add the address with no authentication. In a client configured with an mcpServers file:

{
  "mcpServers": {
    "jade-events": {
      "url": "/api/mcp/agents"
    }
  }
}

A client that only starts local servers, such as Claude Desktop’s configuration file, reaches it through the mcp-remote bridge:

{
  "mcpServers": {
    "jade-events": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "/api/mcp/agents"]
    }
  }
}

Or call it directly. The Accept header must list both application/json and text/event-stream. The answer is one server-sent event carrying the JSON-RPC response, with the tool’s result in both content (as JSON text) and structuredContent. tools/list returns every tool with its input schema.

curl -N /api/mcp/agents \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "search_events",
      "arguments": { "query": "sustainability expo chicago" }
    }
  }'

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"events\":[...]}"}],"structuredContent":{"events":[...]}}}

Signing in

Until the person’s email is proven, an assistant can only search, read a flow’s questions and ask for a code.

  1. Find the event with search_events (words from its name, venue or city, and an optional date range; at most 10 events) or get_event. Each event carries its tenantCode, clientCode and eventCode, and each of its flows a flowSlug and its public registration page. describe_registration lists a flow’s questions, so the assistant can gather the answers first.
  2. Call start_registration with the three codes, the flow, the person’s own email and agentName — the name the person knows the assistant by, up to 40 letters, digits, spaces and plain punctuation. Jade emails a 6-digit code and a Continue in my browser link, saying that an assistant calling itself by that name is registering them. It returns a challengeId; the code expires in 30 minutes. The answer is the same whether the person is new or already registered.
  3. Get the code. An assistant that can read the person’s mailbox finds it in the email from Jade — the subject and the plain-text part both carry it; otherwise ask the person for it. The person can also open the link and carry on in their browser instead.
  4. Call verify_email with the challengeId and the code. It returns sessionToken, participantId, flowSlug, registrationComplete and, when the person has already registered, modifyToken. Three wrong codes cancel the code; call start_registration again.

Passing the session

Most consumer MCP clients cannot set headers or keep an MCP session, so the person’s session travels as tool arguments. Every tool after sign-in takes tenantCode, clientCode, eventCode and sessionToken; the ones that work on a finished registration also take modifyToken.

  • sessionToken is the token the registration page holds for that flow, valid for 24 hours. Every call checks that it belongs to the event named with it.
  • modifyToken is the registration dashboard’s token, valid for one hour and renewed as it is used: every tool that uses it returns a fresh modifyToken, so always pass the newest. submit_registration returns one once it submits.
  • Both act as the person: keep them out of logs and share them with no one. The registrationUrl and paymentUrl links the registration tools return carry the session too, so give them to the person alone.
  • A Test Mode session, or a +1 guest’s session, is refused.

Tools

Jade serves 25 tools on this endpoint, grouped below for reading; every connection gets them all. The Find and Sign-in tools need no session. cancel_room and cancel_registration only preview — the refund, any fee and the terms — unless called with confirm: true; cancel_registration then also needs an operationKey of your own, sent again unchanged on a retry. Confirm only once the person has agreed to the preview.

Group What it covers Tools
Find Search events whose registration accepts personal assistants, read one, and read a flow’s questions. No session needed. search_eventsget_eventdescribe_registration
Sign-in Email the person a 6-digit code and exchange it for the same registration (and modify) session the registration page uses. start_registrationverify_email
Register Answer the flow’s questions, choose products at real prices, apply discount codes, accept policies and submit — under the registration page’s own rules. get_registrationsave_answerslist_productsupdate_cartget_cartapply_discount_codeget_policiesaccept_policysubmit_registration
Housing Search hotels, hold and release rooms, list the booking, join or leave a room waitlist and cancel a booked room. search_hotelshold_roomsrelease_roomlist_my_roomscancel_roomleave_room_waitlist
Change a registration Read a finished registration, change answers, price and submit changes, or cancel — as the modify dashboard does. get_my_registrationupdate_registrationpreview_changessubmit_changescancel_registration

Worked example

Registering Alex for a conference with a paid pass. Calls and results are abridged; every call after verify_email also carries tenantCode, clientCode, eventCode and sessionToken.

// 1. Find the event (no session)
→ search_events { "query": "sustainability expo" }
← { "events": [{
     "tenantCode": "acme-group", "clientCode": "acme", "eventCode": "susexpo-2026",
     "name": "Sustainability Expo 2026", "startOn": "2026-11-18", "endOn": "2026-11-20",
     "timezone": "America/Chicago",
     "flows": [{ "flowSlug": "attendee", "name": "Attendee Registration",
                 "registrationUrl": "/tenant/acme-group/client/acme/event/susexpo-2026/register/attendee" }] }] }

// 2. Email the person a code
→ start_registration { "tenantCode": "acme-group", "clientCode": "acme",
     "eventCode": "susexpo-2026", "flowSlug": "attendee",
     "email": "alex@example.com", "agentName": "Muse" }
← { "status": "code_sent", "challengeId": "c7f1…", "expiresInMinutes": 30, "note": "…" }

// 3. Exchange the code (read from the inbox, or asked of Alex) for the session
→ verify_email { "challengeId": "c7f1…", "code": "421907" }
← { "sessionToken": "eyJ…", "participantId": "6650…", "flowSlug": "attendee",
     "registrationComplete": false, "next": "Use get_registration to see what is still needed." }

// From here on, every call also carries
//   "tenantCode", "clientCode", "eventCode" and "sessionToken"

// 4. What is still needed, then the answers
→ get_registration {}
← { "submitted": false,
     "questions": [{ "binding": "@participant.firstName", "label": "First Name",
                     "type": "text", "required": true }, …],
     "answers": {}, "progress": { "allPagesComplete": false, "pages": [ … ] },
     "registrationUrl": "/tenant/acme-group/client/acme/event/susexpo-2026/register/attendee?token=eyJ…" }
→ save_answers { "answers": { "@participant.firstName": "Alex",
     "@participant.lastName": "Rivera", "@participant.company": "Acme" } }
← { "saved": true, "keys": ["@participant.firstName", "@participant.lastName", "@participant.company"] }

// 5. The cart, at the checkout's prices
→ list_products {}
← { "currency": "USD", "products": [{ "productId": "66a0…", "name": "Full Conference Pass", … }] }
→ update_cart { "items": [{ "productId": "66a0…", "quantity": 1 }] }
← { "changes": [ … ], "cart": { … }, "followUps": [] }
→ get_cart {}
← { /* lines, discounts, tax, fees and total — never quote a price it did not return */ }

// 6. Policies: show the person the text, then record their agreement
→ get_policies {}
← { "policies": [{ "policyId": "65a1…", "title": "Code of Conduct",
                   "required": true, "alreadyAccepted": false, … }],
     "outstandingRequired": 1 }
→ accept_policy { "policyIds": ["65a1…"] }
← { "accepted": ["Code of Conduct"], "alreadyAccepted": [] }

// 7. Submit: the pass costs money, so nothing is submitted yet
→ submit_registration {}
← { "status": "payment_required",
     "amountDue": { "cents": 49500, "currency": "USD" },
     "paymentUrl": "/tenant/acme-group/client/acme/event/susexpo-2026/register/attendee?token=eyJ…",
     "note": "Give the person paymentUrl: they review and pay in their browser. Nothing has been submitted." }

With nothing to pay, the last call submits the registration and returns status: submitted, the orderId when an order was made, and a modifyToken for later changes. With anything required still missing, it returns status: incomplete and a missing list instead.

For someone already registered, verify_email also returns a modifyToken: read the registration with get_my_registration, change answers with update_registration, price the change with preview_changes and apply it with submit_changes.

Payment

Jade confirms card payments in the browser, so the assistant never pays and never sees payment details. When a submit finds money due, or a card the checkout must collect, it submits nothing. A client without URL elicitation gets status: payment_required:

  • From submit_registration: amountDue (cents and currency) and paymentUrl — the registration page, with the person’s session, where they review and pay.
  • From submit_changes: amountDueCents, currency and paymentUrl — the registration dashboard, where the person signs in with their email, pays, and the change is applied.

URL elicitation: the payment link in the chat

A client that declares URL-mode elicitation when it connects (capabilities.elicitation.url, MCP spec 2025-11-25) gets the payment step as a -32042 URL-elicitation error instead of a tool result. The client shows the person the URL and opens it in their browser when they agree. Jade answers the client’s initialize with an Mcp-Session-Id that records the declaration; send it on every later request, as the transport requires.

  • For a registration the URL is a Jade page that carries no session. To prove it is the person, it emails a sign-in link to the address they registered with; that link opens their registration, where they review and pay.
  • For changes it is the registration dashboard, which signs the person in with their email.
  • When the person says they are done, call the tool again with the same arguments. submit_registration then returns status: submitted with a modifyToken; if they have not finished, the same kind of elicitation comes back.

Rooms held with hold_rooms are booked when the registration, or the change, is submitted. Holds expire if the person goes quiet; every housing tool call keeps them alive.

Which flows are offered

An assistant sees a flow only when all of these hold:

  • The organizer turned on Open to personal agents for the flow, in the flow designer under Access. It is off by default.
  • Its access includes Public or Email-verified: the code proves the address, which is all those ask. Invite-only, passcode, directory, SSO and other gated flows are never offered.
  • It is not hidden, not limited to people with certain tags, and has no waiting room. An event’s private guest flows are never offered.
  • The event is published and has not ended or been archived, and the flow is open for registration now. Every tool reads the event’s published version.

Anyone can still register for any other flow on the event’s website.

Limits and errors

  • 60 requests a minute per IP address; beyond that, HTTP 429 Too Many Requests.
  • 3 codes per email address per event per hour. Past that, start_registration still returns an ordinary challengeId, but no email is sent and no code verifies it — so the endpoint never says whether an address was asked about before.
  • 3 wrong codes cancel a code, and its link with it.
  • Failures are tool errors (isError: true), not HTTP errors, with one answer for each kind, so the endpoint cannot be used to probe events or people. An unknown event, or a flow not open to assistants: “No event open to assistant registration matches that. Use search_events to find one.” A bad, expired or mismatched session: “This session is not valid for that event any more. Start again with start_registration.” A wrong, expired or cancelled code: “That code is incorrect or has expired. Call start_registration for a new one.”
  • Every other refusal is the registration page’s own — a sold-out product, a closed sales window, a refused discount code, a read-only field — and comes back with its reason.

Being found

The endpoint is the first entry, personal-agents, in Jade’s MCP discovery document, with authentication: none and every tool it serves:

GET /api/.well-known/mcp.json

The public registration page of a flow open to assistants also puts in its head a schema.org Event as JSON-LD — name, dates, place and a RegisterAction pointing at the page — and a link to the endpoint. The page’s script adds both as it renders:

<script type="application/ld+json">{ "@type": "Event", ..., "potentialAction": { "@type": "RegisterAction", "target": "…/register/attendee" } }</script>
<link rel="mcp-server" href="/api/mcp/agents">

Not supported

  • Payment by the assistant. The person pays in their browser.
  • Flows with a waiting room: admission is enforced in the browser’s queue, which an assistant never stands in.
  • Test Mode, and +1 guests’ sessions.
  • File uploads and signatures. The person adds them on the registration page, at registrationUrl.
  • Organizer, tenant-wide and support tools. Organizers connect their own assistant through the organizer endpoint.

Partner integrations (API account)

The participant endpoint /mcp/participant is for vetted integrators: an assistant you build or host, with an API account a Jade administrator issues. It reaches the events the account is allowed, and your code sets HTTP headers on every request. The assistant submits what it knows about the person, and the person finishes, and pays, at the link Jade returns.

This part walks through the partner flow: getting an API account, connecting to the endpoint, verifying the person’s email with a one-time code, and submitting a registration that the person completes, paying if needed, at the link Jade returns.

Payment always stays with the person.agent_submit_registration saves what the assistant knows about the person and returns a link. When the next step is checkout, the assistant gets that link and never any payment details: the person opens it to pay.

Before you start

  1. You need an API account from a Jade administrator, set up under Developers → API Accounts with Allow an AI assistant to register people ticked (canActAsParticipantAgent: true in the API). It also needs access to the clients or events you will register people for, under Allowed clients or Allowed events: each allowedClients entry is a { tenantCode, clientCode } object and each allowedEvents entry a { tenantCode, clientCode, eventCode } object. If the account is also limited to some tenants under Allowed tenants, each event’s tenant must be one of them. Copy the token when it is issued; it is shown once.
  2. You need the tenant, client and event code of each event: the sign-in tools take all three. They are the codes in the account’s allowed clients and events; ask your Jade administrator if you do not have them.
  3. Your MCP client must let your code set HTTP headers on each request: the API token in Authorization on every call and, once the person is verified, their session token in X-Participant-Session. An assistant you only configure — a custom connector in Claude or ChatGPT, for example — cannot send a different session for each person, so this endpoint is for an assistant you build or host yourself; an assistant you only configure uses the personal-assistant endpoint above.

The server address

Every request goes to the same address:

/api/mcp/participant

Send the API token on every request, the two sign-in calls included, and after sign-in the person’s session token as well:

POST /api/mcp/participant
Authorization: Bearer jdo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Participant-Session: eyJ...session-token...

A missing, revoked or expired token gets 401 Unauthorized, and a token whose API account does not have canActAsParticipantAgent gets 403 Forbidden.

The endpoint is also listed in Jade’s MCP discovery document, as the entry named participant, with its transport (streamable-http), what it needs to sign in and every tool it serves:

GET /api/.well-known/mcp.json

Signing in

The assistant proves it acts for the person with a one-time code sent to their email. The two sign-in tools are the only ones that work before a session exists.

1. Request a code

Call agent_request_otp with the person’s email and the event’s tenantCode, clientCode and eventCode. Jade emails a 6-digit code to that address, and the person reads it back to the assistant. The email opens with your API account’s name, so give the account a name the person will recognise.

// MCP tool call
{
  "name": "agent_request_otp",
  "arguments": {
    "tenantCode": "acme-group",
    "clientCode": "acme",
    "eventCode": "acme-2026",
    "email": "alex@example.com"
  }
}

// Response
{
  "sent": true,
  "expiresInSeconds": 900
}

The code expires after 15 minutes. How many codes can be sent is limited; see Rate limits below.

2. Verify the code and keep the session

// MCP tool call
{
  "name": "agent_verify_otp",
  "arguments": {
    "tenantCode": "acme-group",
    "clientCode": "acme",
    "eventCode": "acme-2026",
    "email": "alex@example.com",
    "code": "421907"
  }
}

If the code matches, Jade returns a session token:

{
  "verified": true,
  "session": {
    "token": "eyJ...session-token...",
    "expiresAt": "2026-05-21T13:14:00.000Z"
  },
  "participantExists": false
}

Send the token in the X-Participant-Session header on every later call. The session is for that one (tenantCode, clientCode, eventCode, email), works only with your API account, and lasts one hour; after that, request a new code. participantExists says whether the event already had a registration for that email.

A wrong code returns verified: false with the reason wrong_code and attemptsRemaining; the third wrong attempt burns the code. While a newer code is still valid, an earlier one counts as a wrong code. The reason expired_or_missing means no unused code matches: it is more than 15 minutes old, has already been used to sign in, was burned by wrong attempts, or was sent for a different event, email or API account. Request a new one.

Using it

With the session token, find the right registration flow, submit what you know about the person, and hand them the link.

1. Find the flows

Call agent_list_flows to see the event’s registration flows (attendee, exhibitor, speaker and so on) with the fields each one asks for. Use it to choose the flow for the person and to see what you still need to ask them.

// MCP tool call (with X-Participant-Session)
{
  "name": "agent_list_flows",
  "arguments": {}
}

// Response (abridged)
{
  "flows": [
    {
      "slug": "primary-attendee",
      "name": "Attendee Registration",
      "visibleForTags": null,
      "descriptor": {
        "descriptor": {
          "eventCode": "acme-2026",
          "flowSlug": "primary-attendee",
          "fields": [
            {
              "binding": "@participant.firstName",
              "flatKey": "firstName",
              "entityPath": "participant.firstName",
              "label": "First Name",
              "type": "text",
              "source": "standard",
              "required": true
            },
            {
              "binding": "@custom.tShirtSize",
              "flatKey": "tShirtSize",
              "entityPath": "customFieldResponses.tShirtSize",
              "label": "T-Shirt Size",
              "type": "choice",
              "source": "custom",
              "required": false,
              "customFieldCode": "tShirtSize",
              "choices": [
                { "code": "M", "name": "Medium" },
                { "code": "L", "name": "Large" }
              ]
            }
          ]
        },
        "customFields": [ /* the event's own questions: key, name, responseType, choices */ ],
        "timezone": "America/New_York",
        "eventStartOn": "2026-10-12"
      }
    },
    {
      "slug": "exhibitor",
      "name": "Exhibitor Registration",
      // Shown only to people with the exhibitor tag
      "visibleForTags": { "type": "all", "required": ["exhibitor"] },
      // Same shape as above; null if the flow could not be described
      "descriptor": { /* ... */ }
    }
  ]
}

2. Submit the registration

Send everything you know about the person. The more fields you fill in now, the fewer the person has to fill in when they open the link. Leave out flowSlug and the event’s first visible flow is used.

// MCP tool call (with X-Participant-Session)
{
  "name": "agent_submit_registration",
  "arguments": {
    "flowSlug": "primary-attendee",
    "data": {
      "@participant.firstName": "Alex",
      "@participant.lastName":  "Rivera",
      "@custom.tShirtSize":     "L",
      "@custom.dietaryNotes":   "vegetarian"
    }
  }
}

// Response (new registration)
{
  "status": "new",
  "matchStatus": "new",
  "url": "/tenant/acme-group/client/acme/event/acme-2026/register/primary-attendee?token=eyJ...registration-token...",
  "jwt": "eyJ...registration-token...",
  "participantId": "507f1f77bcf86cd799439011",
  "flowSlug": "primary-attendee"
}

Field keys use the @entity.property token format: @participant.* for built-in fields and @custom.* for the event’s own questions. The session’s verified email is always saved as @participant.email, so a session cannot register a different address.

3. Hand off to the person

The response carries:

  • status (and matchStatus, the same value): new for a new registration, existing_incomplete when the person had already started one, which the link continues, or existing_complete when they are already registered.
  • url — the link the person opens to finish, payment included. For existing_complete it is instead the sign-in page for changing a registration, with their email filled in.
  • jwt — the registration token already in the link, valid for 24 hours; submit again for a fresh link. It is not returned for existing_complete.
  • flowSlug — the flow used (not for existing_complete) — and, for a new registration only, participantId.
  • When a field fails validation the response is instead status: validation_failed with an errors list, and nothing is saved. Fix the fields and submit again.

Give the person the link. It opens their registration without another sign-in, so send it to them alone; the rest of the flow, payment included, happens in their browser.

4. After they finish

Use get_my_registration, check_balance_due and describe_my_flow to confirm the registration or fetch its details. They act on the person’s own registration, so they need a session issued once it exists: if agent_verify_otp returned participantExists: false, they answer “No verified participant identity for this run.” Request and verify a new code after agent_submit_registration, and use the new session.

The same goes for the recommendation tools — recommend_products, recommend_hotel, recommend_companies and recommend_participants — and generate_participant_profile. They work from the registered person’s details, so with a session issued before the registration existed they answer “No participant identity available”.

Tools

Jade serves 23 tools on this endpoint, grouped below for reading. Unlike the organizer endpoint there is no way to pick groups: every connection gets them all. Every tool except agent_request_otp and agent_verify_otp needs the session token, and most of the rest act on the person’s own registration, so they need a session issued after it exists (see After they finish).

Group What it covers Tools
Sign-in Email the person a one-time code and exchange it for their session. The only tools that work before a session exists. agent_request_otpagent_verify_otp
Registration List the flows, submit a registration, then read and update the person’s own registration, their balance due and their confirmation email. agent_list_flowsagent_submit_registrationdescribe_my_flowget_my_registrationupdate_my_registrationcheck_balance_dueresend_my_confirmation_emaillist_data_fields
Housing The event’s hotels, and a hotel recommendation for the person. get_hotel_listrecommend_hotel
Groups Register the members of the person’s group from a template. get_group_import_templateimport_group_members
Exhibitors & appointments The booth map, exhibitor availability, and booking or cancelling the person’s appointments. get_booth_maplist_exhibitor_availabilitybook_exhibitor_appointmentlist_my_appointmentscancel_my_appointment
Recommendations Products, exhibiting companies and other participants suited to the person, from their profile, once the session knows the registered person. recommend_productsrecommend_companiesrecommend_participantsgenerate_participant_profile

What is not available, and why

The endpoint acts for one verified person at one event, so it serves only what that person may do for themselves. Left out:

  • Working through the form with the person: the cart, the product list, policy agreements, moving between form pages and form progress. Jade’s own in-registration assistant has these because it sits beside the form the person has open; here the person does those steps in their browser, at the link.
  • Payment. The assistant never sees payment details; the person pays at the link agent_submit_registration returns.
  • Organizer tools — event setup, other people’s registrations, reports and exports. They need an organizer’s own Jade login, through the organizer endpoint.
  • Tenant-wide tools — queries across events, creating events and account administration.
  • Jade’s support-inbox tools, and the tools an exhibiting company’s administrators use for their company.

Actions that cannot be undone

The assistant acts as the person, and some tools send email or change what Jade holds about them. Once one has run, the assistant cannot take it back:

  • agent_request_otp emails a sign-in code to the address it is given.
  • agent_submit_registration creates a registration for the person from the data it is sent. When the event already has one with the same name and email, it saves nothing and links to that one instead.
  • update_my_registration changes the person’s saved answers.
  • import_group_members registers someone else as a member of the person’s group, with a hotel reservation when a stay is sent in plain keys such as hotelId.
  • book_exhibitor_appointment books the appointment and shares the person’s details with the exhibitor as a lead. Jade emails the person a confirmation and tells the exhibitor’s contact.
  • cancel_my_appointment cancels the appointment and emails the other side — the other participant, or the exhibitor’s contact. A cancelled booth slot is free for anyone to book.
  • resend_my_confirmation_email sends one of the person’s emails to them again. Called without an emailId, it only lists the ones it can resend.
  • generate_participant_profile writes a short AI-generated description of the person and saves it on their record, replacing any earlier one.

None of them takes payment: the person pays in their browser, at the link agent_submit_registration returns. Before each one, the assistant should tell the person what it is about to do — the address a code goes to, the answers it will save, the member it will add, the appointment it will book or cancel, the email it will resend — and go ahead only once they agree.

Revoking access

A Jade administrator can revoke the API token, untick Allow an AI assistant to register people, or remove the account’s access to a tenant, client or event, under Developers → API Accounts. Each change applies from the next request: the token and the account’s access are checked again on every call, so sessions already issued stop working too.

Sessions also end on their own after one hour.

Rate limits

Sign-in codes are limited to 3 per email address per hour and 200 per API account per day, so an assistant cannot work through an address book. Past either limit, agent_request_otp returns a tool error — “OTP rate limit exceeded for this email — try again in an hour” or “Daily OTP quota exceeded for this API account” — rather than an HTTP 429. Jade sets no per-minute request limit on this endpoint; the API account’s rateLimitPerMinute applies to the REST API only.

Troubleshooting

401 “Missing Authorization header” or “Invalid or revoked API token”
Send Authorization: Bearer and the token on every request, the two sign-in calls included. A revoked or expired token, or one whose API account has been deactivated, fails at once; ask your Jade administrator for a new one.
403 “API account is not enabled for the participant MCP endpoint”
The account does not have Allow an AI assistant to register people ticked. Ask your Jade administrator to tick it under Developers → API Accounts.
“API account is not authorized for …”
The tenant, client and event codes are not covered by the account’s allowed tenants, clients or events. Check the codes, or ask your Jade administrator to add the event.
“Event … does not belong to client …”
The event is under a different client. Check the client code you passed with it.
agent_verify_otp returns verified: false
With wrong_code, attemptsRemaining says how many tries are left; while a newer code is still valid, an earlier one counts as wrong. With expired_or_missing, no unused code matches: it is more than 15 minutes old, has already been used to sign in, was burned by 3 wrong attempts, or was sent for a different event, email or API account. Request a new one.
“Missing X-Participant-Session header — call agent_verify_otp first”
Every tool except the two sign-in tools needs the session token from agent_verify_otp, sent in the X-Participant-Session header.
“Invalid or expired participant session”
A session lasts one hour and works only with the API account that verified it. Request and verify a new code.
“This API account is no longer authorized for this event.”
The account’s access to that event was removed after the session was issued; every call checks it again. Ask your Jade administrator.
“No verified participant identity for this run.” or “No participant identity available” just after you registered someone
The session was issued before the person’s registration existed (participantExists: false). Request and verify a new code, then use the new session.
agent_submit_registration returns status: validation_failed
errors lists what to fix, and nothing was saved. Check the fields against the flow’s descriptor from agent_list_flows, then submit again.
“OTP rate limit exceeded for this email …” or “Daily OTP quota exceeded for this API account”
A limit on sign-in codes was reached (see Rate limits). Wait: the per-email limit counts the codes sent to that address in the last hour, and the per-account limit the codes the account sent in the last 24 hours.

Related