Public previewSee what changed →
All guides

For developers

Page 9 of 9

The sequence below talks to the server directly. Every request is a POST of one JSON-RPC message (a batch is refused) with these two headers, and each call stands alone: the server keeps no session between calls.

text
Content-Type: application/json
Accept: application/json, text/event-stream

GET https://api-staging-core.livingsocial.com/mcp_customers/info answers the server's name, version and the instructions it gives an assistant, with no MCP handshake.

Start with initialize. It answers the protocol version the server speaks, its capabilities and its instructions to the assistant.

bash
curl -s -X POST "https://api-staging-core.livingsocial.com/mcp_customers/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "curl-example", "version": "1.0.0" }
    }
  }'

List the tools. Each entry carries the tool's name, its description, which states what the tool changes, and its input schema.

bash
curl -s -X POST "https://api-staging-core.livingsocial.com/mcp_customers/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
  }'

Call a tool. This one searches for massage offers in Chicago:

bash
curl -s -X POST "https://api-staging-core.livingsocial.com/mcp_customers/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "search_offers",
      "arguments": { "query": "massage", "city": "Chicago", "limit": 5 }
    }
  }'

The answer is a JSON-RPC result whose content holds one text block, and the text is the answer as JSON. Shortened, it looks like this:

text
{
  "journey": { "id": "...", "token": "grpn_...", "identityState": "anonymous", "synthetic": false, "created": true },
  "offers": [ ... ],
  "query": { "normalized": "massage", "matched": [ ... ] },
  "observedAt": "..."
}

journey.token is there only because this call started the journey. Take it from that answer and send it as journeyToken in the arguments of the next call:

bash
curl -s -X POST "https://api-staging-core.livingsocial.com/mcp_customers/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 4,
    "method": "tools/call",
    "params": {
      "name": "get_offer",
      "arguments": {
        "offer": "<an offer id or permalink from the search>",
        "journeyToken": "<the journey.token from the search>"
      }
    }
  }'

A client that can set headers may send the token as Authorization: Bearer <token> instead. When both are present, the header wins.

Preparing a purchase takes the ids from get_offer. The person must already have chosen the option and the quantity and agreed to the price:

bash
curl -s -X POST "https://api-staging-core.livingsocial.com/mcp_customers/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 5,
    "method": "tools/call",
    "params": {
      "name": "prepare_checkout",
      "arguments": {
        "journeyToken": "<the journey.token from the search>",
        "offerId": "<offer.id from get_offer>",
        "optionId": "<offer.options[].id from get_offer>",
        "quantity": 1,
        "operationKey": "swedish-massage-order-0001",
        "confirmSelection": true
      }
    }
  }'

Its answer carries journey, checkout (with checkoutId, status, lines, the total in minor units with its currency and exponent, and nextAction), created (true when this call made the checkout, false on a replay), handoffUrl, handoffExpiresAt and otherOpenCheckouts. Pass checkout.checkoutId to get_purchase_status to read the checkout later.

Prices come as an integer in minor units next to a currency and an exponent: 4500 with an exponent of 2 is $45.00. Each offer carries the same facts as the public reads described in LivingSocial for agents.

Errors

A refusal comes back marked as an error, and its text holds a code, a message and a retry hint, plus retryAfterSeconds and details where they apply:

text
{ "error": { "code": "journey_invalid", "message": "...", "retry": "never" } }

retry says whether to try the same call again:

retryMeaning
safeYes, try again.
after_fixOnly after changing the arguments, or after doing what the message says.
waitTry again after retryAfterSeconds, or after a short wait when none is given.
neverDo not repeat the call.

A purchase-related call never answers safe.

CodeMeaningretry
invalid_inputAn argument is missing or not valid. The message names the field.after_fix
journey_requiredThe tool needs a journeyToken. Call search_offers or get_offer first and carry the token they return.after_fix
journey_invalidThe token is unknown or has expired. Explain the lost access. Start a new session only when the person wants to start again; it cannot read the previous cart. The old token is refused again.never
offer_not_foundNo LivingSocial offer has that id or permalink.never
offer_unavailableThe offer or option cannot be bought right now: it is expired, sold out or has no active option.never
unsupported_purchaseThe purchase cannot be made through LivingSocial yet, for example an offer sold as a time-slot booking.never
price_changedThe price changed. details carries the current price (currentPriceMinor, currency, currencyExponent). Show it to the person. If they agree, call prepare_checkout again with a new operationKey.after_fix
operation_key_reusedThe operationKey was already used for a different selection. A different selection needs its own key.never
checkout_in_progressAnother call is preparing this checkout right now, or the person already has a checkout open on Groupon for this cart (one cart, one open checkout). Ask again with the same operationKey, or let the person finish or close the open one first; cartUrl shows it to them.wait
rate_limitedToo many requests for this session or this address.wait
upstream_unavailableLivingSocial's offers or the checkout cannot be reached right now.safe for a read, wait for prepare_checkout (call again with the same operationKey)
unknown_toolThe server has no tool with that name.never
internal_errorSomething went wrong on LivingSocial's side. For prepare_checkout, nothing was charged.safe for a read, wait otherwise (for prepare_checkout, call again with the same operationKey)