Skip to content
iJava Cafe logo
Developer docsv1.2.0

iJava Developers

MCP server & REST API

Let any AI assistant read the iJava menu, place a pickup order, and check on it. Or give your own app a key and order from a prepaid balance.

POSThttps://ijavacafe.com/api/mcp

Overview

Two ways to order real food from iJava Cafe in San Jose from software. The MCP server lets an AI assistant read the cafes and the live menu, build a pickup order, hand the customer a secure payment link, and check the order afterwards. The REST API lets your own app place orders from a prepaid balance that go straight to the kitchen.

It speaks the Model Context Protocol, so Claude, Cursor and any other MCP client can use it with no SDK and no glue code.

Endpointhttps://ijavacafe.com/api/mcp
TransportStreamable HTTP, stateless. One JSON-RPC 2.0 message per POST, answered with JSON.
Protocol versions2025-06-18, 2025-03-26, 2024-11-05
AuthenticationNone. See Authentication and limits.
FulfilmentPickup only. AI orders are open at Willow Glen.
PaymentThe customer pays on a Stripe page. The server never charges a card by itself.

Open by design

The MCP server has no API keys, no customer accounts and no saved cards. This is on purpose, so any assistant can connect in a minute. An app that should order automatically on its own card uses the REST API instead, which has keys and billing.

  • Your agent fills in the customer. It passes the customer's name, email and phone to create_order when it places the order.
  • The customer confirms by paying. They open the payment link, check the items and the total, and pay on Stripe's page.
  • Nothing is charged without the customer. The server never stores a card and never charges one by itself. An unpaid order never reaches the kitchen.

What is available

PieceStatus
MCP tools: list_locations, search_menu, create_order, get_order_statusLive
REST API /api/v1 with keys, a sandbox and prepaid credits — see REST APILive
Webhooks: order.paid, order.printed, order.refunded, order.canceled — see WebhooksLive

Quickstart

Add the server to your client, then ask it something like "What cold drinks does iJava Willow Glen have?"

claude mcp add --transport http ijava https://ijavacafe.com/api/mcp

No key, no sign-up and no customer account. Every tool works the moment the client connects.

How ordering works

  1. Pick a cafe. Call list_locations. Use a cafe where online_pickup_open is true.
  2. Find the items. Call search_menu to get each item_id, its sizes, and its required options.
  3. Confirm with the customer. Read back the items and the price before you order.
  4. Create the order. Call create_order. You get an order_number and a payment_url.
  5. Hand over the link. The server also emails and texts it to the customer by default. They pay on Stripe's page, and the order goes to the kitchen the moment they pay.
  6. Check on it. Call get_order_status with the order number and the customer's email.
Note.Every order works this way. The agent prepares the order, and the customer approves it by paying. There are no saved cards and no automatic charges, so the customer always sees the total before they pay.

When a cafe is not taking online pickup orders, create_order refuses with a link where the customer can order instead. Pass that link on.

Authentication and limits

The server needs no key, and that will not change. The tools only read the public menu, or create an order the customer still has to pay for. Customer details are never returned, except to someone who already has both the order number and the email.

Your agent supplies the customer's name, email and phone on each create_order call. There is no customer account to sign in to, and no token to store. Use the details your user gave you, and confirm them before you order. The receipt goes to that email, and get_order_status needs it.

Rate limits

LimitPer
120 requests a minutecaller
5 create_order calls a minutecaller
20 get_order_status calls a minutecaller
30 create_order calls a minuteall callers together

Past a limit the server answers HTTP 429 with JSON-RPC error -32000. Wait a minute and try again. Request bodies over 64 KB get HTTP 413.

list_locations

Read-only

List iJava cafes

iJava cafe locations with address, phone, weekly hours, and whether online pickup orders are open right now.

Arguments

None. Send an empty object, {}.

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_locations",
    "arguments": {}
  }
}
Result (the text of content[0], parsed)
[
  {
    "location": "delmas",
    "name": "iJava Cafe — Delmas",
    "address": "387 Delmas Ave, San Jose, CA 95126",
    "phone": "(408) 753-9732",
    "hours": {
      "sun": "06:30-14:00",
      "mon": "06:30-14:00",
      "…": "…"
    },
    "online_pickup_open": false,
    "order_elsewhere": "https://order.toasttab.com/online/ijava-cafe-delmas-387-delmas-avenue"
  },
  {
    "location": "almaden",
    "name": "iJava Cafe — Willow Glen",
    "address": "2306 Almaden Rd #150, San Jose, CA 95125",
    "phone": "(408) 979-0251",
    "hours": {
      "sun": "08:00-14:00",
      "mon": "08:00-14:00",
      "…": "…"
    },
    "online_pickup_open": true,
    "next_pickup": "ASAP"
  }
]

Hours are Pacific time, 24-hour clock. When online_pickup_open is true the result has next_pickup (ASAP, or the next opening such as Saturday at 8:00 AM). When it is false the result has order_elsewhere, a link the customer can order from instead.

Try itCalls the live server

search_menu

Read-only

Search the menu

Search one cafe's orderable menu. Returns item_id, price, sizes and modifiers, which create_order needs. Prices are in USD before tax.

Arguments

location
"delmas" | "almaden"required

delmas = Downtown San Jose, almaden = Willow Glen

query
string

Words to match in the item name, description or section. Omit to list the whole menu. (max 80 chars)

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_menu",
    "arguments": {
      "location": "almaden",
      "query": "cold brew"
    }
  }
}
Result (the text of content[0], parsed)
{
  "location": "almaden",
  "count": 1,
  "items": [
    {
      "item_id": "cold-brew",
      "name": "Cold Brew",
      "section": "Coffee",
      "price": "$7.00",
      "modifiers": [
        {
          "name": "Milk",
          "choose": "exactly 1 (required)",
          "options": [
            "Regular milk",
            "Oat milk (+$0.75)",
            "Black (no milk)"
          ]
        },
        {
          "name": "Extras",
          "choose": "any (optional)",
          "options": [
            "Vanilla (+$0.75)",
            "Extra shot (+$0.75)"
          ]
        }
      ]
    }
  ]
}

Every word in query must match the item name, description, section or category. Omit query to get the whole orderable menu for that cafe (about 120 items).

Sized items return sizes, and you must send one of them as size in create_order. A modifier marked required must be sent. Send option names without the (+$0.75) suffix. Items the cafe has run out of today carry "sold_out": true and will be refused.

Try itCalls the live server

create_order

Creates an order

Create a pickup order

Prices a pickup order and returns a secure Stripe payment link. By default the link is also emailed and texted to the customer. Nothing is charged until the customer opens the link and pays. The kitchen gets the order once payment goes through. Confirm the items and total with the customer before calling this.

Arguments

location
"delmas" | "almaden"required

delmas = Downtown San Jose, almaden = Willow Glen

customer
objectrequired

The person picking up

customer.name
stringrequired

Name on the pickup ticket (max 120 chars)

customer.email
stringrequired

Gets the receipt; needed for get_order_status (email, max 320 chars)

customer.phone
stringrequired

The cafe calls this number if there is a problem (max 40 chars)

lines
object[]required

One entry per menu item. At most 40 items in all (1–40 items)

lines[].item_id
stringrequired

From search_menu

lines[].quantity
integerrequired

How many of this item (1–20)

lines[].size
string

Required when the item has sizes; one of its sizes

lines[].modifiers
object[]

Option names exactly as search_menu lists them, without the price suffix

lines[].modifiers[].name
stringrequired

—

lines[].modifiers[].options
string[]

—

lines[].special_instructions
string

Note for this item (max 280 chars)

special_instructions
string

Note for the whole order (max 500 chars)

notify_email
boolean

Email the payment link to customer.email (default true)

notify_sms
boolean

Text the payment link to customer.phone. Sent only once iJava has texting for AI orders switched on; the result says if it was skipped (default true)

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_order",
    "arguments": {
      "location": "almaden",
      "customer": {
        "name": "Jordan Lee",
        "email": "jordan@example.com",
        "phone": "408-555-0100"
      },
      "lines": [
        {
          "item_id": "cold-brew",
          "quantity": 2,
          "modifiers": [
            {
              "name": "Milk",
              "options": [
                "Oat milk"
              ]
            }
          ]
        }
      ],
      "special_instructions": "Light ice"
    }
  }
}
Result (the text of content[0], parsed)
{
  "order_number": "IJ-261003-4214BB",
  "payment_url": "https://checkout.stripe.com/c/pay/cs_live_…",
  "payment_link_expires_in_minutes": 30,
  "status": "pending_payment",
  "notifications": {
    "email": "sent",
    "sms": "sent"
  },
  "next_step": "We sent the payment link to the customer by email and text. Also give them payment_url. The order goes to the kitchen when they pay."
}
Important.Confirm the items and the price with the customer before you call this tool. The customer pays on Stripe's page, so nothing is charged until they do, and an unpaid order never reaches the kitchen.

Prices, tax and the pickup time are set by the server. The payment page shows the final total with tax. The link stays valid for 30 minutes.

Email and text

By default the server sends the payment link to the customer by email and by text, so they can pay from their phone even if they never see your app. Set notify_email or notify_sms to false to skip one, for example when your app shows the link itself.

notifications valueMeaning
sentOn its way to the customer.
offYou set it to false.
skipped: …Not sent, with the reason. For example, texting for AI orders is not switched on yet, the number replied STOP, or this email or phone already had 3 AI orders in the last hour.
failedThe send did not go through. The order is fine; hand over payment_url yourself.

The messages hold only the order number, the items, the cafe, the pickup time and the link. The customer's name and your notes are never included.

No “Try it” for this tool: a test call would create a real order and a real payment link. Use the example above from your own client.

get_order_status

Read-only

Check an order

Status, pickup time, items and total for an order. Needs the order number and the email it was placed with.

Arguments

order_number
stringrequired

e.g. IJ-261002-A1B2C3, returned by create_order (max 40 chars)

email
stringrequired

The email the order was placed with (email)

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_order_status",
    "arguments": {
      "order_number": "IJ-261002-4214BB",
      "email": "jordan@example.com"
    }
  }
}
Result (the text of content[0], parsed)
{
  "order_number": "IJ-261002-4214BB",
  "status": "paid",
  "location": "almaden",
  "cafe": "iJava Cafe — Willow Glen",
  "address": "2306 Almaden Rd #150, San Jose, CA 95125",
  "pickup": "ASAP",
  "items": [
    {
      "name": "Cold Brew",
      "quantity": 2,
      "price": "$15.50"
    }
  ],
  "subtotal": "$15.50",
  "total_paid": "$16.95"
}
statusMeaning
pending_paymentLink created, the customer has not paid yet.
paidPaid. The order is with the cafe. refunded appears if staff refunded any of it.
payment_failedCheckout could not start or did not complete.
canceledStaff canceled the order.

A wrong email and an unknown order number get the same answer, No order matches that number and email.

Try itCalls the live server

REST API overview

For your own app or service: order from iJava with a key, from a prepaid balance, with no customer in the loop. Three resources, locations, menu and orders. A live order is paid from your credits and prints in the kitchen at Delmas or Willow Glen the moment you place it. Pickup only; customers walk in.

Base URLhttps://ijavacafe.com/api/v1
FormatJSON in and out. Money is integer cents in USD. Times are ISO 8601; the cafes run on America/Los_Angeles.
AuthenticationAuthorization: Bearer ijava_live_… or ijava_test_…, from the developer console.
PaymentPrepaid credits, like an AI token API: load a balance by card, every live order draws it down, and auto top-up refills it below a threshold you set. The MCP server, by contrast, never charges a card itself.
  1. Create an account with your email and make a test key.
  2. Read the menu, place a test order, look at the response.
  3. Add credits on the Billing page (the first load comes with $5 on us), make a live key, and ship.
First call
curl https://ijavacafe.com/api/v1/me \
  -H "Authorization: Bearer ijava_test_…"

Test and live keys

KeyValidates and pricesCharges your cardPrints in the kitchenShows up in
ijava_test_…Yes, identicallyNoNoYour console, test list
ijava_live_…YesDraws down your credits at placementYes, within secondsKitchen, kiosk, admin, your console

Test orders go through the same pricing, modifier validation, sold-out checks and totals as live ones. The only differences: nothing is charged, nothing prints, and status is always paid.

Keys, credits and GET /me

Send your key as a bearer token on every request. The middle word of the key is its mode. A key is shown once when you create it; revoke it in the console and it stops working on the next request.

http
Authorization: Bearer ijava_live_4f1c…
Important.Keep keys on a server. A key in a browser or a mobile app is a key anyone can use to buy breakfast on your card.

Credits and auto top-up

Load credits on the console's Billing page, $5 to $2,000 at a time, paid on Stripe's page; the card you pay with is saved. Each live order draws menu price plus San Jose sales tax from the balance, no platform fee. There is no per-order cap: an order is refused only when the balance cannot cover it (insufficient_balance).

Switch on auto top-up and set two numbers: a threshold and a top-up amount. When an order would take the balance under the threshold, the saved card loads the top-up amount first, in the same request, and the order goes through. An order bigger than the top-up amount loads the shortfall plus the amount. A declined card only fails the order when the balance could not have covered it alone.

Refunds and cancellations happen at the counter and come straight back as credit. The ledger on the Billing page shows every movement with the balance after it.

`GET /me`

bash
curl https://ijavacafe.com/api/v1/me -H "Authorization: Bearer ijava_test_…"
200 OK
{
  "object": "account",
  "id": "a1c2…",
  "name": "Sam Rivera",
  "company": "Breakfast Bot",
  "email": "sam@example.com",
  "key": {
    "id": "…",
    "name": "Breakfast bot",
    "mode": "test",
    "prefix": "ijava_test_ab12cd34"
  },
  "balance": {
    "cents": 8420,
    "auto_top_up": {
      "enabled": true,
      "threshold_cents": 2500,
      "amount_cents": 10000
    }
  },
  "billing": {
    "card_on_file": true,
    "card": {
      "brand": "visa",
      "last4": "4242"
    }
  }
}

GET /locations

The two cafes you can order from. accepting_orders is the operator's switch: when it is false, live orders for that cafe return ordering_paused. next_pickup_at is null when an order placed now is made right away; otherwise it is the next opening it would be scheduled for.

200 OK
{
  "object": "list",
  "data": [
    {
      "id": "almaden",
      "object": "location",
      "name": "iJava Cafe — Willow Glen",
      "short_name": "Willow Glen",
      "address": "2306 Almaden Rd #150, San Jose, CA 95125",
      "phone": "(408) 979-0251",
      "timezone": "America/Los_Angeles",
      "hours": {
        "mon": [
          {
            "open": "08:00",
            "close": "14:00"
          }
        ],
        "…": []
      },
      "open_now": true,
      "accepting_orders": true,
      "next_pickup_at": null,
      "kitchen_printer": "online"
    },
    {
      "id": "delmas",
      "…": "…"
    }
  ]
}

GET /locations/{location}/menu

Everything orderable at one cafe, with the id you pass when ordering. Prices are flat or sized; for a sized item pass the size label as size on the order line, for example "size": "Bowl" on the tomato bisque.

Modifiers: selection is one (pick exactly one), count (pick exactly count), or many (any number, at most max when set). one and count modifiers are required. Option prices are added per option; a modifier's own price_cents is added once when it is used.

available is false when the cafe has marked an item sold out today. Ordering it returns item_unavailable.

200 OK (abridged)
{
  "object": "menu",
  "location": "almaden",
  "categories": [
    {
      "id": "breakfast",
      "title": "Breakfast",
      "sections": [
        {
          "id": "burritos",
          "title": "Burritos",
          "items": [
            {
              "id": "fully-loaded-burrito",
              "name": "Fully Loaded Burrito",
              "description": "Beef bacon, eggs, cheddar jack, salsa, potatoes, spinach, avocado, chipotle sour cream",
              "price": {
                "kind": "flat",
                "cents": 1900
              },
              "modifiers": [
                {
                  "name": "Side",
                  "selection": "one",
                  "required": true,
                  "count": null,
                  "max": null,
                  "price_cents": 0,
                  "options": [
                    {
                      "value": "Salad",
                      "price_cents": 0
                    },
                    {
                      "value": "Fruit",
                      "price_cents": 0
                    },
                    {
                      "value": "No side",
                      "price_cents": 0
                    }
                  ]
                },
                {
                  "name": "Extras",
                  "selection": "many",
                  "required": false,
                  "count": null,
                  "max": 9,
                  "price_cents": 0,
                  "options": [
                    {
                      "value": "Extra avocado",
                      "price_cents": 200
                    },
                    {
                      "value": "…",
                      "price_cents": 0
                    }
                  ]
                }
              ],
              "flags": [
                "popular"
              ],
              "station": "food",
              "available": true
            }
          ]
        }
      ]
    }
  ]
}

POST /orders

One request places, charges and sends the order. The response is the order as the kitchen sees it. Send an Idempotency-Key header (or idempotency_key in the body) and a retry of the same request returns the order you already placed, with Idempotent-Replayed: true, instead of a second charge.

location
"delmas" | "almaden"required

The cafe.

customer.name
stringrequired

Who picks it up. Goes on the ticket.

customer.email
stringrequired

Gets the confirmation email.

customer.phone
stringrequired

Staff call it if something is wrong.

items
object[]required

1–40 lines, 40 items in total.

items[].item_id
stringrequired

From the menu.

items[].quantity
integerrequired

1–20.

items[].size
string

Required for sized prices: the size label.

items[].modifiers
object[]

{ name, options[] } per modifier used. Required modifiers must be present.

items[].note
string

Up to 280 characters, printed on the ticket.

note
string

Order-level note, up to 500 characters.

idempotency_key
string

Alternative to the header.

Request
curl https://ijavacafe.com/api/v1/orders \
  -H "Authorization: Bearer ijava_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sam-breakfast-2026-10-08" \
  -d '{
    "location": "almaden",
    "customer": { "name": "Sam Rivera", "email": "sam@example.com", "phone": "+14085550100" },
    "items": [
      { "item_id": "fully-loaded-burrito", "quantity": 1,
        "modifiers": [{ "name": "Side", "options": ["Fruit"] },
                      { "name": "Extras", "options": ["Extra avocado"] }] },
      { "item_id": "latte", "quantity": 1,
        "modifiers": [{ "name": "Temperature", "options": ["Iced"] },
                      { "name": "Milk", "options": ["Oat milk"] }] }
    ],
    "note": "Pulling in at 8:40."
  }'
201 Created
{
  "id": "6c1f1d0e-…",
  "object": "order",
  "mode": "live",
  "order_number": "IJ-261008-3F9A2C",
  "status": "paid",
  "location": "almaden",
  "pickup": {
    "at": null,
    "label": "ASAP"
  },
  "customer": {
    "name": "Sam Rivera",
    "email": "sam@example.com",
    "phone": "+14085550100"
  },
  "items": [
    {
      "item_id": "fully-loaded-burrito",
      "name": "Fully Loaded Burrito",
      "quantity": 1,
      "size": null,
      "modifiers": [
        {
          "name": "Side",
          "options": [
            "Fruit"
          ],
          "price_cents": 0
        },
        {
          "name": "Extras",
          "options": [
            "Extra avocado"
          ],
          "price_cents": 200
        }
      ],
      "note": null,
      "unit_cents": 2100,
      "line_cents": 2100,
      "station": "food"
    },
    {
      "item_id": "latte",
      "name": "Latte",
      "quantity": 1,
      "size": null,
      "modifiers": [
        {
          "name": "Temperature",
          "options": [
            "Iced"
          ],
          "price_cents": 0
        },
        {
          "name": "Milk",
          "options": [
            "Oat milk"
          ],
          "price_cents": 75
        }
      ],
      "note": null,
      "unit_cents": 675,
      "line_cents": 675,
      "station": "drinks"
    }
  ],
  "note": "Pulling in at 8:40.",
  "subtotal_cents": 2775,
  "tax_cents": 260,
  "total_cents": 3035,
  "refunded_cents": 0,
  "payment": {
    "method": "balance",
    "balance_after_cents": 5385
  },
  "kitchen": {
    "printed_at": null
  },
  "created_at": "2026-10-08T15:40:12.000Z",
  "paid_at": "2026-10-08T15:40:13.000Z"
}

Payment. payment.balance_after_cents is what is left on your credits once this order is paid; it is null when you read the order back later. payment.method is test for sandbox orders.

Pickup timing. During opening hours, with at least 30 minutes before close, an order is made right away and pickup.at is null. Otherwise it is scheduled for the next opening and pickup.at says when. Both cases print; a scheduled order prints with its time.

The kitchen. kitchen.printed_at is set by the cafe's printer box once the ticket has come out, usually within ten seconds. Poll the order to see it.

Emails. The customer gets a confirmation from iJava with the order number and pickup time. Staff get the ticket by email as well, marked with your account name.

GET /orders/{id} and GET /orders

GET /orders/{id} returns one order with its current status: paid, part_refunded, refunded, canceled or payment_failed. Refunds and cancellations are done by staff at the counter and show here, with refunded_cents for the amount sent back to your card.

GET /orders?limit=20 lists your account's orders in the key's mode, newest first, up to 100.

bash
curl "https://ijavacafe.com/api/v1/orders?limit=5" -H "Authorization: Bearer ijava_live_…"

Webhooks

Register an endpoint in the console (Webhooks tab) and we POST a signed JSON event when something happens to one of your orders. The kitchen and counter events come from the cafe itself: the printer box stamps the ticket, staff refund or cancel at the kiosk, and the event follows within about 15 seconds.

EventWhen
order.paidThe order was paid from your balance and sent to the kitchen. Sent at once.
order.printedThe kitchen printer box has printed the ticket.
order.refundedStaff refunded some or all of it at the counter. Sent once per refund; refunded_cents is the running total.
order.canceledStaff canceled the order.
Delivery body
{
  "id": "evt_5f0c…",
  "object": "event",
  "type": "order.printed",
  "created_at": "2026-10-08T15:40:21.000Z",
  "data": {
    "order": {
      "id": "6c1f1d0e-…",
      "order_number": "IJ-261008-3F9A2C",
      "status": "paid",
      "kitchen": {
        "printed_at": "2026-10-08T15:40:19.000Z"
      },
      "…": "…"
    }
  }
}

Verifying a delivery

Every delivery carries X-iJava-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" with your endpoint's secret. Compute it over the raw bytes, compare in constant time, and reject timestamps older than five minutes. X-iJava-Event names the type and X-iJava-Delivery is unique per attempt.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, rawBody, header) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Retries

Answer with any 2xx within 10 seconds. Anything else is retried with backoff — 1 minute, 5, 15, 30, then 1, 2, 6 and 12 hours — eight tries in all. An endpoint that fails 100 deliveries in a row is switched off; the console shows every delivery and lets you switch it back on. Deliveries can arrive out of order and, rarely, twice: key on id.

“Send test” in the console posts a sample order.paid with "test": true so you can check your verifier before the first real order.

REST errors and limits

Every error is the same shape, with a stable code to branch on.

422 Unprocessable Entity
{
  "error": {
    "code": "invalid_request",
    "message": "Choose a size for Tomato Bisque."
  }
}
HTTPcodeMeaning
400invalid_requestBody did not parse or failed validation; fields lists what.
401unauthorizedMissing, malformed, revoked or unknown key.
402insufficient_balanceNot enough credits for this order, and auto top-up did not cover it. Nothing was ordered.
402payment_declinedAuto top-up tried the saved card and it was declined. Nothing was ordered.
404not_foundUnknown location or order.
409item_unavailableSomething in the order is sold out today.
422invalid_requestA line could not be priced: bad item, size or modifier.
429rate_limitedSlow down; Retry-After says how long.
503ordering_pausedThe cafe's app-and-AI ordering switch is off right now.
5xxserver_errorOur side. Retry with the same idempotency key.

Limits

  • 120 read requests a minute and 20 order requests a minute per key.
  • 40 items per order, 20 of any one line. No per-order dollar cap: the balance is the limit.
  • Credits load from $5 to $2,000 at a time.
  • Twenty active keys per account.
  • Request bodies up to 32 KB.

Errors

There are two kinds. A tool error means the tool ran and said no: bad arguments, a sold-out item, a closed cafe. It comes back as a normal result with isError: true and a sentence your model can read and act on.

Tool error
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Complete Milk for Cold Brew."
      }
    ],
    "isError": true
  }
}

A protocol error means the request itself was wrong. It comes back as a JSON-RPC error.

CodeHTTPMeaning
-32700400The body is not valid JSON.
-32600400 / 413Not a single JSON-RPC 2.0 message, or the body is over 64 KB.
-32601200Unknown method. The server supports initialize, ping, tools/list and tools/call.
-32602200Unknown tool name.
-32000429Rate limited.

Raw protocol

You only need this to call the server without an MCP client. Every request is a POST to the endpoint with one JSON-RPC message. There are no sessions: you can skip initialize and call tools/call directly.

List the tools
curl -s https://ijavacafe.com/api/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Call a tool
curl -s https://ijavacafe.com/api/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_locations","arguments":{}}}'

The result of tools/call is { content: [{ type: "text", text }] }, where text is JSON. tools/list returns each tool's full JSON Schema as inputSchema. Notifications get HTTP 202 with no body. GET and DELETE get 405, because there is no event stream and no session to end. CORS is open, so a browser page can call the server directly.

Changelog

DateVersionChange
2026-10-081.2.0REST API at /api/v1 for apps that order from a prepaid balance: developer accounts, test and live keys, credits with auto top-up at /ai/console. Signed webhooks for paid, printed, refunded and canceled orders. Documented under REST API.
2026-10-031.1.0create_order emails and texts the payment link to the customer by default, with notify_email and notify_sms to turn either off, and returns notifications. AI orders open at Willow Glen.
2026-10-031.0.1Docs: the server is open by design. No API keys, customer accounts or saved cards are planned. The customer confirms every order by paying.
2026-10-021.0.0First release: list_locations, search_menu, create_order, get_order_status.