> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.goautolane.com/courier/muse-connector/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.goautolane.com/_mcp/server. # Muse courier connector Autolane's courier connector lets an AI agent hand a curbside food order to an Autolane vehicle. The agent places the order on the store's website in the user's own name. An Autolane vehicle collects it at the curb and drives it to the user's address. Muse, Meta's agent, is the first client. This page is the full reference for agent developers and reviewers: who can use the connector, how clients authenticate, the six tools, every response and error, payment, limits, and how Autolane handles data. ## At a glance | Item | Value | | ------------------------- | --------------------------------------------------------------------------------------------------------------------- | | MCP endpoint | `https://mcp.goautolane.com/api/delivery-mcp/mcp` | | Transport | Streamable HTTP, stateless. Send every request as a `POST`; `GET` and `DELETE` answer `405`. | | Authentication | OAuth 2.1 authorization code flow with PKCE (`S256`). Public clients only: no API keys, no client secret. | | Scope | `courier:write` | | OAuth resource (RFC 8707) | `https://mcp.goautolane.com/api/delivery-mcp` | | Client registration | Dynamic (RFC 7591) at `https://mcp.goautolane.com/api/delivery-mcp/oauth/register` | | User sign-in | The user's mobile number, then a 6-digit code sent by SMS | | Tools | `check_delivery`, `reserve_delivery`, `confirm_delivery`, `update_delivery`, `get_delivery_status`, `cancel_delivery` | | MCP resource | `courier://description` (Markdown): the connector's instructions to the agent | | Service area | Austin, Texas | | Delivery fee | \$4.95, charged by Autolane separately from the store order | | Support | [support@goautolane.com](mailto:support@goautolane.com) | ## Access requirements ### People During the pilot, access is by invitation: Autolane enables each mobile number. A number that is not enabled sees this message at the phone step, and no code is sent: > Autolane Courier isn't available for this number yet. If Autolane removes a number later, it loses access at its next MCP call (`401`) or token refresh (`invalid_grant`), whichever comes first. Each person needs: * A mobile number that receives SMS, entered with its country code. A 10-digit number typed without `+` is read as a US number (`+1` is added). * A store and a drop-off address inside the same Autolane service area in Austin. * To accept the delivery terms and the text-message consent that the agent shows before it reserves a vehicle. ### Clients An agent client must support: * MCP over Streamable HTTP. * The OAuth 2.1 authorization code flow with PKCE `S256`, sending the RFC 8707 `resource` parameter. * Discovery through RFC 9728 (protected resource metadata) and RFC 8414 (authorization server metadata). * RFC 7591 dynamic client registration with a redirect URI that Autolane has allowlisted. To add a redirect URI, email [support@goautolane.com](mailto:support@goautolane.com). * Showing the user the price, the terms and the consent text before the reserve. * Taking payment for the delivery fee through the hosted Stripe Checkout link or a Stripe shared payment token. * Placing the store order on the store's website in the user's own name. ### What Autolane serves * **Stores.** Restaurants and other prepared-food businesses with curbside pickup: restaurants, cafes, bakeries and takeout. The store must appear on Google Maps at the address given, with a phone number and opening hours, and the agent must list curbside among the store's pickup options. * **Stores Autolane refuses,** whatever the cart holds: liquor stores, pharmacies and drugstores, convenience stores, gas stations, supermarkets, grocery and food stores, markets, warehouse, discount and general stores, wholesalers, bars, pubs, breweries, wineries and night clubs, casinos, smoke shops, and entertainment venues such as karaoke bars, comedy clubs, dance halls, live music venues, bowling alleys and cinemas. * **Check-in.** The vehicle cannot use a store's own app. A store whose only check-in is its app is declined (`app_only_check_in`). * **Timing.** Autolane offers 15-minute pickup windows from the store's lead time up to 90 minutes ahead, inside both the store's opening hours and Autolane's service hours (8 AM to 8 PM, store time). It does not take orders for later than that. * **Restricted items.** Never use Autolane for alcohol, tobacco, vape and nicotine products, pharmacy and prescription items, lottery, firearms and ammunition, or any order that needs an ID or an age check at pickup. Autolane screens the items on every order and will not deliver them. * **Pickup requirements.** An order is not serviceable when pickup needs an ID, a name match with the person collecting, a signature or an age check (`id_required`, `name_must_match`, `signature`, `age_restricted`). Autolane never provides a pickup person's name or a license plate. If checkout requires either and accepts nothing else, the order is not serviceable. ## Authentication Autolane's authorization server is the MCP resource itself. Its issuer is `https://mcp.goautolane.com/api/delivery-mcp`, and every OAuth endpoint sits under that path. While Autolane has the connector paused, the MCP endpoint, the four OAuth endpoints and the authorization server metadata answer `404`. ### Step 1: get the 401 Call the MCP endpoint with no token. The `WWW-Authenticate` header points to the protected resource metadata. ```http POST /api/delivery-mcp/mcp HTTP/1.1 Host: mcp.goautolane.com Content-Type: application/json Accept: application/json, text/event-stream {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example-agent","version":"1.0.0"}}} ``` ```http HTTP/1.1 401 Unauthorized Content-Type: application/json WWW-Authenticate: Bearer error="invalid_token", error_description="No authorization provided", resource_metadata="https://mcp.goautolane.com/.well-known/oauth-protected-resource/api/delivery-mcp" {"error":"invalid_token","error_description":"No authorization provided"} ``` ### Step 2: read the protected resource metadata (RFC 9728) ```http GET /.well-known/oauth-protected-resource/api/delivery-mcp HTTP/1.1 Host: mcp.goautolane.com ``` ```json { "resource": "https://mcp.goautolane.com/api/delivery-mcp", "authorization_servers": ["https://mcp.goautolane.com/api/delivery-mcp"] } ``` The response carries `Cache-Control: max-age=3600` and allows any origin. ### Step 3: read the authorization server metadata (RFC 8414) The issuer has a path, so under RFC 8414 section 3.1 the metadata is served only at `/.well-known/oauth-authorization-server/api/delivery-mcp`. The root `/.well-known/oauth-authorization-server` has nothing for this server. ```http GET /.well-known/oauth-authorization-server/api/delivery-mcp HTTP/1.1 Host: mcp.goautolane.com ``` ```json { "issuer": "https://mcp.goautolane.com/api/delivery-mcp", "authorization_endpoint": "https://mcp.goautolane.com/api/delivery-mcp/oauth/authorize", "token_endpoint": "https://mcp.goautolane.com/api/delivery-mcp/oauth/token", "registration_endpoint": "https://mcp.goautolane.com/api/delivery-mcp/oauth/register", "revocation_endpoint": "https://mcp.goautolane.com/api/delivery-mcp/oauth/revoke", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"], "revocation_endpoint_auth_methods_supported": ["none"], "scopes_supported": ["courier:write"] } ``` | Field | Value | | -------------------------------------------- | ------------------------------------------------------------- | | `issuer` | `https://mcp.goautolane.com/api/delivery-mcp` | | `authorization_endpoint` | `https://mcp.goautolane.com/api/delivery-mcp/oauth/authorize` | | `token_endpoint` | `https://mcp.goautolane.com/api/delivery-mcp/oauth/token` | | `registration_endpoint` | `https://mcp.goautolane.com/api/delivery-mcp/oauth/register` | | `revocation_endpoint` | `https://mcp.goautolane.com/api/delivery-mcp/oauth/revoke` | | `response_types_supported` | `["code"]` | | `grant_types_supported` | `["authorization_code", "refresh_token"]` | | `code_challenge_methods_supported` | `["S256"]` | | `token_endpoint_auth_methods_supported` | `["none"]` | | `revocation_endpoint_auth_methods_supported` | `["none"]` | | `scopes_supported` | `["courier:write"]` | The response carries `Cache-Control: public, max-age=300` and allows any origin. ### Step 4: register the client (RFC 7591) ```http POST /api/delivery-mcp/oauth/register HTTP/1.1 Host: mcp.goautolane.com Content-Type: application/json { "client_name": "Example Agent", "redirect_uris": ["https://agent.example.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"] } ``` | Field | Required | Rule | | ---------------------------- | -------- | ------------------------------------------------------------------------ | | `client_name` | Yes | 1 to 100 characters after trimming. The phone page shows it to the user. | | `redirect_uris` | Yes | 1 to 10 URLs, each up to 2,000 characters. | | `token_endpoint_auth_method` | No | Only `none`. | | `grant_types` | No | `authorization_code`, `refresh_token`. | | `response_types` | No | Only `code`. | Other fields are ignored. Every redirect URI must be an absolute `https` URL with no fragment and must match an entry on Autolane's allowlist exactly, character for character. To add a redirect URI, email [support@goautolane.com](mailto:support@goautolane.com). ```http HTTP/1.1 201 Created Content-Type: application/json Cache-Control: no-store { "client_id": "mc_k3Jd9sLq2VwX8rT1nB4yZa", "client_name": "Example Agent", "redirect_uris": ["https://agent.example.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "client_id_issued_at": 1791244800 } ``` Autolane issues no client secret. Errors: | Status | `error` | `error_description` | When | | ------ | ------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------- | | 400 | `invalid_client_metadata` | `client_name and one to ten redirect_uris are required` | The body is not valid JSON, is over 65,536 bytes, or breaks a rule above. | | 400 | `invalid_client_metadata` | `client_name is required` | `client_name` is blank after trimming. | | 400 | `invalid_redirect_uri` | `Every redirect_uri must be on the Autolane allowlist` | A redirect URI is not `https`, has a fragment, or is not on the allowlist. | | 429 | `invalid_request` | `Too many registrations; wait and retry` | More than 20 registrations in an hour from one address. | | 500 | `server_error` | `Internal error` | Autolane fault. | ### Step 5: send the user to authorize Open the authorization endpoint in the user's browser. ```text https://mcp.goautolane.com/api/delivery-mcp/oauth/authorize ?response_type=code &client_id=mc_k3Jd9sLq2VwX8rT1nB4yZa &redirect_uri=https%3A%2F%2Fagent.example.com%2Foauth%2Fcallback &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &resource=https%3A%2F%2Fmcp.goautolane.com%2Fapi%2Fdelivery-mcp &scope=courier%3Awrite &state=af0ifjsldkj ``` | Parameter | Required | Rule | | ----------------------- | ----------- | ------------------------------------------------------------- | | `response_type` | Yes | `code` | | `client_id` | Yes | The `client_id` from registration. | | `redirect_uri` | Yes | One of the client's registered redirect URIs, exactly. | | `code_challenge` | Yes | 43 base64url characters: the SHA-256 of your `code_verifier`. | | `code_challenge_method` | Yes | `S256` | | `resource` | Yes | `https://mcp.goautolane.com/api/delivery-mcp`, exactly. | | `scope` | No | `courier:write`, the default and the only scope. | | `state` | Recommended | Up to 1,024 printable ASCII characters, returned unchanged. | **What the user sees.** 1. **Phone page.** Titled "Link your phone", with the heading "Link your phone to" and the client's `client_name`, and the line "Autolane texts a code to the number that will get delivery updates." The user enters a mobile number and taps **Text me a code**. Spaces, dots, dashes and parentheses are ignored. A number typed without `+` is read as follows: 10 digits gain `+1` (a US number); any other length gains `+` and is read as starting with its country code. The result must be in E.164 form (`+` and 7 to 15 digits). 2. **Code page.** Titled "Enter your code": "We texted a 6-digit code to the number ending" and the last four digits. The user enters the code and taps **Link phone**. 3. **Redirect.** Autolane links the number and redirects the browser to your `redirect_uri` with `code` and `state`. ```http HTTP/1.1 302 Found Location: https://agent.example.com/oauth/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj Cache-Control: no-store ``` The request is good for 10 minutes from the first page load. The authorization code is good for 60 seconds and works once. **Errors shown on the page, never redirected.** These come before Autolane trusts the redirect URI (OAuth 2.1 section 4.1.2.1), or after the user has started the phone step. | Status | Message | When | | ------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | 400 | `client_id and redirect_uri are required` | Either parameter is missing. | | 400 | `Unknown client` | The `client_id` is not registered. | | 400 | `redirect_uri is not registered for this client` | The `redirect_uri` does not match one the client registered. | | 400 | `This link has expired. Go back to the app and try linking again.` | The user submitted a form more than 10 minutes after the first page load, or the form was altered. | | 400 | `Start again from the app.` | The form reached the code step without a phone number. | | 429 | `Too many requests. Try again in a minute.` | More than 60 page requests in a minute from one address. | | 500 | `Something went wrong. Try again.` | Autolane fault. | **Errors redirected to the client.** Once the client and redirect URI pass, faults go back to `redirect_uri` with `error`, `error_description` and `state` (when you sent one). | `error` | `error_description` | When | | --------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `invalid_request` | `state is too long or not printable ASCII` | `state` is over 1,024 characters or holds characters outside printable ASCII. | | `unsupported_response_type` | `response_type must be code` | `response_type` is not `code`. | | `invalid_request` | `PKCE S256 code_challenge is required` | `code_challenge` is missing or not 43 base64url characters, or `code_challenge_method` is not `S256`. | | `invalid_target` | `resource must be the courier MCP resource` | `resource` is missing or not exactly `https://mcp.goautolane.com/api/delivery-mcp`. | | `invalid_scope` | `Only courier:write is supported` | `scope` holds anything other than `courier:write`. | **Messages at the phone and code steps.** These show on the page, and the user can try again. | Status | Message | When | | ------ | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | 400 | `Enter a mobile number with its country code.` | The number is not valid E.164 after cleanup. | | 403 | `Autolane Courier isn't available for this number yet.` | Autolane has not enabled this number. No code is sent. | | 400 | `That number cannot receive text messages. Check it and try again.` | The number is a landline, invalid, or blocked for SMS. | | 429 | `Too many codes sent to this number. Try again later.` | More than 5 codes in an hour to one number. | | 429 | `Too many codes sent from your connection. Try again later.` | More than 20 codes in an hour from one address. | | 503 | `We could not send a code right now. Try again in a minute.` | The SMS provider failed. | | 400 | `That code is not right. Try again.` | Wrong code. | | 400 | `That code expired. Send a new one.` | The code expired. The phone page shows again. | | 429 | `Too many attempts. Wait ten minutes, then request a new code.` | More than 10 code checks in 10 minutes for one number. A new code does not reset this. | | 503 | `We could not check that code right now. Try again in a minute.` | The SMS provider failed. | ### Step 6: exchange the code for tokens The token endpoint takes `application/x-www-form-urlencoded` only. Send `client_id` in the body; there is no client secret. ```http POST /api/delivery-mcp/oauth/token HTTP/1.1 Host: mcp.goautolane.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fagent.example.com%2Foauth%2Fcallback &code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk &client_id=mc_k3Jd9sLq2VwX8rT1nB4yZa &resource=https%3A%2F%2Fmcp.goautolane.com%2Fapi%2Fdelivery-mcp ``` (Line breaks added for reading; send one line.) `code`, `redirect_uri` and `code_verifier` are required. `redirect_uri` must equal the one used at authorize. `code_verifier` is 43 to 128 characters from `A-Z a-z 0-9 - . _ ~`. `resource` is optional here; when sent it must equal the resource of the authorization request. ```http HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "access_token": "example-access-token", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "example-refresh-token", "scope": "courier:write" } ``` Tokens are opaque strings. Every error has the body `{"error": "...", "error_description": "..."}`: | Status | `error` | `error_description` | When | | ------ | ------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------- | | 400 | `invalid_request` | `Body must be application/x-www-form-urlencoded` | Wrong content type, or a body over 16,384 bytes. | | 401 | `invalid_client` | `client_id is required` | No `client_id`. | | 401 | `invalid_client` | `Unknown client` | The `client_id` is not registered. | | 429 | `invalid_request` | `Too many token requests; wait and retry` | Over 120 requests a minute for one `client_id`, or 240 from one address. | | 400 | `invalid_request` | `code, redirect_uri and code_verifier are required` | A field is missing. | | 400 | `invalid_grant` | `The authorization code is unknown` | No such code. | | 400 | `invalid_grant` | `The authorization code has expired` | More than 60 seconds old. | | 400 | `invalid_grant` | `The authorization code was already used` | A second exchange. Autolane also revokes every token the first exchange issued. | | 400 | `invalid_grant` | `The code or token belongs to another client` | The code or refresh token was issued to a different `client_id`. | | 400 | `invalid_grant` | `redirect_uri does not match the authorization request` | Different `redirect_uri`. | | 400 | `invalid_grant` | `code_verifier does not match` | The verifier is malformed or does not hash to the challenge. | | 400 | `invalid_target` | `resource does not match` | `resource` differs from the authorization request. | | 400 | `invalid_request` | `refresh_token is required` | Refresh grant with no `refresh_token`. | | 400 | `invalid_grant` | `The refresh token is unknown` | No such refresh token. | | 400 | `invalid_grant` | `The refresh token has expired` | More than 30 days old. | | 400 | `invalid_grant` | `The refresh token was revoked` | A revoked refresh token was reused. Autolane revokes the whole token family. | | 400 | `invalid_grant` | `Not a refresh token` | An access token was sent as `refresh_token`. | | 400 | `invalid_grant` | `This user is not enabled for Autolane Courier` | Autolane removed the user's number. | | 400 | `unsupported_grant_type` | `grant_type must be authorization_code or refresh_token` | Any other grant type. | | 500 | `server_error` | `Internal error` | Autolane fault. | ### Step 7: refresh ```http POST /api/delivery-mcp/oauth/token HTTP/1.1 Host: mcp.goautolane.com Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=example-refresh-token&client_id=mc_k3Jd9sLq2VwX8rT1nB4yZa ``` The response has the same shape as step 6. Refresh tokens rotate: each refresh revokes the token you sent and returns a new access token and a new refresh token. Store the new refresh token and drop the old one. If a refresh token is used a second time, Autolane revokes every token in its family, and the user must link again. The refresh also checks that Autolane still enables the user. If not, it answers `400` `invalid_grant` with `This user is not enabled for Autolane Courier`, and issues no tokens. Drop the refresh token. A new authorization attempt stops at the phone step. ### Step 8: revoke (RFC 7009) ```http POST /api/delivery-mcp/oauth/revoke HTTP/1.1 Host: mcp.goautolane.com Content-Type: application/x-www-form-urlencoded token=example-refresh-token&client_id=mc_k3Jd9sLq2VwX8rT1nB4yZa ``` Revoking a refresh token revokes its whole family, including live access tokens. Revoking an access token revokes only that token. The answer is `200` with an empty body even when the token is unknown or belongs to another client. `token` and `client_id` are required (`400 invalid_request`, `token and client_id are required`). The rate limit is the token endpoint's (`429`, `Too many requests; wait and retry`). ### Step 9: call the MCP endpoint Send the access token as a bearer token on every request. ```http POST /api/delivery-mcp/mcp HTTP/1.1 Host: mcp.goautolane.com Authorization: Bearer example-access-token Content-Type: application/json Accept: application/json, text/event-stream {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_delivery_status","arguments":{"delivery_id":"dlv_4fG7hJ2kL9mN3pQ5rS8tVw"}}} ``` The endpoint answers `401` with the header and body from step 1 when: * the request has no `Authorization: Bearer` header; * the token is unknown, expired or revoked, is a refresh token, or was issued for another resource; * Autolane no longer enables the user's number. On a `401`, refresh once. If the refresh fails with `invalid_grant`, run authorization again. An access token belongs to one user: every delivery it creates or reads is scoped to that user, and another user's `delivery_id` reads as `not_found`. ### Token lifetimes | Item | Lifetime | | ------------------------------------------------ | ----------------------------- | | Authorization request (the phone and code pages) | 10 minutes | | Authorization code | 60 seconds, single use | | Access token | 1 hour (`expires_in: 3600`) | | Refresh token | 30 days, rotated on every use | ## Call flow One `delivery_id` covers a delivery from the first check to the end. Follow this order: 1. **Check.** Call `check_delivery` before checkout starts, with the store, the drop-off address, the store's pickup options, the timing and any store pickup slots. It answers serviceable or a decline, with the price, `pickup_windows` (best first), the terms, the consent text and `quote_expires_at`. After a decline, do not place the store order for Autolane delivery. 2. **Show.** Show the user the price, the terms text and the consent text. 3. **Reserve.** At checkout, call `reserve_delivery` with the customer's name, consent and terms acceptance. Omit `contact.phone` or send the number the user linked. It holds a vehicle for 15 minutes and returns `pickup_profile` (what to enter at checkout), `pickup_time.select`, `hold_expires_at` and the `payment` request. 4. **Pay.** Get the user's approval for the fee and send the payment credential before you submit the store order. See [Payment](#payment). 5. **Order.** Submit the store order in the user's name, then call `confirm_delivery` right away with the order number, the items and the confirmation text (and the pickup code when the store shows one). It returns `planned_pickup_time`, `estimated_delivery_window` and `tracking_url`. 6. **Track.** Poll `get_delivery_status` and follow `next_step`. The user can follow the delivery through `tracking_url`. Use `update_delivery` for anything that changes after the reserve. If the user abandons the order or picks another store after a reserve, call `cancel_delivery` so the vehicle is released. ### Clocks | Clock | Length | What happens when it runs out | | -------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Quote (`quote_expires_at`) | 30 minutes from `check_delivery` | A delivery with no store order reads `expired`. `reserve_delivery` answers `quote_expired`: call `check_delivery` again. A reserve pushes the quote to at least 30 minutes after the hold ends. | | Hold (`hold_expires_at`) | 15 minutes from `reserve_delivery` | The delivery reads `quoted` again. If no store order was submitted, `update_delivery` (any type but `payment`) answers `hold_expired` until you call `reserve_delivery` again. | | Pickup horizon | 90 minutes from now | Windows reach no further. A ready time beyond it answers `timing_not_feasible`. | | Payment retry | 10 more minutes | When `confirm_delivery` answers `payment_failed` while the hold is live, Autolane extends the hold by 10 minutes so you can send a new credential. | | Hosted Checkout link | 35 minutes | The `checkout_url` stops working. Call `reserve_delivery` again for a fresh link. | If the hold ran out before you submitted the store order, call `reserve_delivery` again first. If you submitted the order without a live hold, `confirm_delivery` takes a new hold itself; when no vehicle is free it declines the delivery with `no_capacity` instead of booking it. ## Tools | Tool | Title | Use | | --------------------- | ---------------- | -------------------------------------------------------------------------- | | `check_delivery` | Check delivery | Ask whether Autolane can deliver, and get the price and terms. | | `reserve_delivery` | Reserve delivery | Hold a vehicle at checkout and get the pickup profile and payment request. | | `confirm_delivery` | Confirm delivery | Report the placed store order and book the delivery. | | `update_delivery` | Update delivery | Report payment or any change after the reserve. | | `get_delivery_status` | Delivery status | Read the delivery's state. | | `cancel_delivery` | Cancel delivery | Cancel before the vehicle collects the order. | The server also offers one resource, `courier://description` (title "Autolane courier connector", `text/markdown`). It holds the connector's instructions to the agent: the call order, the restricted goods, the payment rules and the check-in rules. ### Rules every tool shares **Idempotency.** Every tool except `get_delivery_status` takes an `idempotency_key`: 8 to 128 characters, with no NUL byte. Keys are scoped to the linked user and kept for 48 hours. * When you retry a call, send the same key and the same arguments. * A stored success or decline replays: a retry returns the first answer again (with a fresh `tracking_url` where one applies). * The same key with different arguments answers `idempotency_conflict`. * A refusal (an envelope with an `error`) is never stored, so a retry with the same key runs the call again. * A retry that arrives while the first call is still running waits up to 10 seconds, then answers `internal_error` with `A call with this idempotency_key is still in flight`. Retry again with the same key. **Address object** (`store_address`, `dropoff_address`): | Field | Type | Required | Rule | | ---------- | ------ | -------- | ----------------------------------------------- | | `street` | string | Yes | 1 to 255 characters | | `unit` | string | No | Up to 64 characters | | `city` | string | Yes | 1 to 255 characters | | `state` | string | Yes | 1 to 64 characters | | `zip` | string | Yes | 1 to 20 characters | | `location` | object | No | `lat` from -90 to 90 and `lng` from -180 to 180 | **Other shared types:** | Type | Rule | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `delivery_id` | `dlv_` followed by 22 letters and digits, for example `dlv_4fG7hJ2kL9mN3pQ5rS8tVw`. | | Time | RFC 3339 with `Z` or a numeric offset, for example `2026-10-06T23:45:00.000Z`. Autolane returns UTC. | | Retailer slot | `id` (optional, up to 64 characters), `start` (time), `end` (time), `label` (optional, up to 100 characters). | | Item | `name` (1 to 200 characters), `quantity` (whole number from 1 to 999), `modifiers` (optional, up to 20 strings of up to 100 characters). | | Phone | E.164: `+` then 7 to 15 digits, first digit 1 to 9, for example `+15125550100`. | **Result shape.** Every call the MCP layer accepts returns a `CallToolResult` with: * `structuredContent`: the response envelope (see [Responses](#responses)); * `content`: one `text` item holding the same envelope as JSON; * `isError: true` only when the envelope's `error.code` is `internal_error`. Other refusals, such as `rate_limited`, `invalid_state` or `not_found`, are ordinary results with an `error` object in the envelope. Arguments that fail a tool's input schema (a missing field, a wrong type, a string too long) never reach Autolane's code: the MCP layer refuses them with its own validation error naming the field, with no envelope, `next_step` or `user_message`. Fix the field and retry. Every envelope has `next_step` (the exact call to make next) and `user_message` (one sentence you may show the user). Follow `next_step`. ### check\_delivery Asks whether Autolane can deliver a curbside order from this store to this address. Call it before checkout starts. It creates the delivery and returns its `delivery_id`. | Field | Type | Required | Rule | | ------------------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------- | | `retailer_name` | string | Yes | 1 to 200 characters. The store's name as the website shows it. | | `store_address` | Address | Yes | The store's address. | | `store_url` | string | No | A URL, up to 2,000 characters. | | `dropoff_address` | Address | Yes | Where the vehicle delivers. | | `dropoff_notes` | string | No | Up to 500 characters. | | `requested_timing` | object | Yes | `{ "type": "asap" }`, or `{ "type": "window", "start": time, "end": time }`. | | `pickup_methods_offered` | array | Yes | 1 to 10 of `curbside`, `counter`, `drive_thru`, `locker`, `delivery`, `other`. Must include `curbside`. | | `retailer_slots` | array | No | Up to 50 retailer slots, when the store offers pickup slots. | | `item_count` | integer | No | 0 to 1,000. | | `idempotency_key` | string | Yes | 8 to 128 characters. | ```json { "retailer_name": "Example Taqueria", "store_address": { "street": "123 Example St", "city": "Austin", "state": "TX", "zip": "78701" }, "dropoff_address": { "street": "456 Sample Ave", "unit": "Apt 2", "city": "Austin", "state": "TX", "zip": "78704" }, "dropoff_notes": "Blue door at the back", "requested_timing": { "type": "asap" }, "pickup_methods_offered": ["curbside", "counter"], "item_count": 3, "idempotency_key": "check-7f3a9c21" } ``` **Response.** A serviceable check returns `state: "quoted"` and these fields: | Field | Meaning | | ------------------ | ------------------------------------------------------------------------------------------------ | | `serviceable` | `true` | | `price` | `{ "amount": "4.95", "currency": "usd" }`. `amount` is a string in dollars. | | `pickup_windows` | 15-minute windows, best first: `start`, `end`, and `slot_id` when a window matches a store slot. | | `terms` | `version` and `text`. Show the text to the user. | | `consent` | `version` and `text`. Show the text to the user. | | `quote_expires_at` | When the quote lapses (30 minutes). | ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "state": "quoted", "next_step": "Show the user the price, terms and consent text; at checkout call reserve_delivery with the customer's name, phone, consent and terms acceptance", "user_message": "Autolane can deliver this order for $4.95; the vehicle collects it curbside.", "retailer_name": "Example Taqueria", "store_address": { "street": "123 Example St" }, "serviceable": true, "price": { "amount": "4.95", "currency": "usd" }, "pickup_windows": [ { "start": "2026-10-06T23:45:00.000Z", "end": "2026-10-07T00:00:00.000Z" }, { "start": "2026-10-07T00:00:00.000Z", "end": "2026-10-07T00:15:00.000Z" } ], "terms": { "version": "2026-09-27", "text": "Autolane collects your curbside order from the store and delivers it to the address you gave. The delivery fee is charged by Autolane, separately from your order. If the store cannot hand over the order, if it contains items Autolane cannot carry, or if you cancel after the vehicle has collected it, Autolane may still charge the fee. Autolane is not responsible for the contents, quality or availability of your order." }, "consent": { "version": "2026-09-27", "text": "By providing your phone number you agree that Autolane may send you text messages about this delivery, including a tracking link and arrival updates. Message and data rates may apply. Reply STOP to opt out at any time and HELP for help." }, "quote_expires_at": "2026-10-06T23:55:00.000Z" } ``` The check carries no `payment_status` or `fee_cents`; those appear after the reserve. **Declines.** `state: "declined"`, `serviceable: false` and a `decline_reason`: `no_curbside`, `store_not_supported`, `disqualifying_requirement`, `out_of_zone`, `outside_service_hours`, `no_capacity` or `timing_not_feasible`. `next_step` is "Tell the user Autolane cannot deliver this order; do not place the retailer order for Autolane delivery". A decline is stored, so a retry with the same key returns it again. **Errors.** `rate_limited`; `idempotency_conflict`; `internal_error`, including `Store lookup is unavailable; retry with the same idempotency_key` and `Address lookup is unavailable; retry with the same idempotency_key`. ### reserve\_delivery Holds an Autolane vehicle for 15 minutes at checkout. Returns what to enter at checkout and the payment request. | Field | Type | Required | Rule | | ----------------------------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | | `delivery_id` | string | Yes | From `check_delivery`. | | `customer` | object | Yes | `first_name` and `last_name`, each 1 to 100 characters. | | `contact` | object | Yes | May be `{}`. `phone` (optional, E.164) must be the number the user linked. `email` (optional) up to 254 characters. | | `contact_consent` | object | Yes | `granted: true` and `consent_version: "2026-09-27"`. | | `terms_acknowledged` | object | Yes | `accepted: true` and `terms_version: "2026-09-27"`. | | `pickup_requirements_seen` | array | Yes | May be `[]`. Up to 10 of `none`, `id_required`, `name_must_match`, `signature`, `age_restricted`, `other`: what checkout shows. | | `pickup_requirements_notes` | string | No | Up to 1,000 characters. | | `alternate_pickup_person_supported` | boolean | No | Accepted and ignored. | | `check_in_method` | string | No | `email_link`, `order_page`, `call_store`, `unknown` or `app_only`, when known. | | `intended_slot` | Retailer slot | No | The store slot you plan to pick, when known. | | `payment` | object | No | `shared_payment_token`, 1 to 200 characters. See [Payment](#payment). | | `idempotency_key` | string | Yes | 8 to 128 characters. | ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "customer": { "first_name": "Sam", "last_name": "Rivera" }, "contact": { "email": "sam@example.com" }, "contact_consent": { "granted": true, "consent_version": "2026-09-27" }, "terms_acknowledged": { "accepted": true, "terms_version": "2026-09-27" }, "pickup_requirements_seen": ["none"], "check_in_method": "order_page", "idempotency_key": "reserve-7f3a9c21" } ``` **Response.** `state: "held"` with: | Field | Meaning | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | `payment_status`, `fee_cents` | See [Payment](#payment). `fee_cents` is `495` today. | | `pickup_profile.vehicle` | `make`, `model`, `color` and `description`. Enter `description` where checkout asks about the vehicle. | | `pickup_profile.pickup_phone` | The phone number to enter at checkout. It takes voice calls only. | | `pickup_profile.pickup_phone_note` | `Voice calls only; this number does not receive texts` | | `pickup_profile.order_contact_email` | `Use the customer's own email for the order` | | `pickup_profile.notes_for_retailer` | Text for the store's pickup notes field. | | `pickup_time` | `select` and `instruction`: the pickup slot to choose at checkout. | | `hold_expires_at` | When the hold ends (15 minutes). | | `payment` | `amount_cents`, `currency`, `seller_profile_id`, `checkout_url` (while no payment is authorized) and `instruction`. | | `released_delivery_ids` | Earlier holds for this user that this reserve released. | `pickup_time` depends on the store: * Stores where the kitchen starts when the vehicle checks in: `select` is `asap`, with "Place the order now; the vehicle checks in on arrival and the kitchen starts then". * Stores with pickup slots, when a slot fits: `select` is the slot's label, id, or start and end, with "Select the retailer pickup slot" and the slot name, then "at checkout". * Stores with pickup slots, when none was chosen: `select` is a time, with "Select the earliest retailer pickup slot at or after" that time. ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "state": "held", "next_step": "Get the user's approval for the fee and send the payment credential with update_delivery (update_type payment) before submitting the retailer order; then fill checkout with pickup_profile and pick the slot in pickup_time.select", "user_message": "An Autolane vehicle is reserved until 6:45 PM; complete checkout now.", "retailer_name": "Example Taqueria", "store_address": { "street": "123 Example St" }, "payment_status": "pending", "fee_cents": 495, "pickup_profile": { "vehicle": { "make": "Tesla", "model": "Model Y", "color": "Grey", "description": "Grey Tesla Model Y" }, "pickup_phone": "+15125550100", "pickup_phone_note": "Voice calls only; this number does not receive texts", "order_contact_email": "Use the customer's own email for the order", "notes_for_retailer": "Curbside pickup by an Autolane vehicle. The vehicle will check in on arrival. Call the pickup phone with questions." }, "pickup_time": { "select": "asap", "instruction": "Place the order now; the vehicle checks in on arrival and the kitchen starts then" }, "hold_expires_at": "2026-10-06T23:45:00.000Z", "payment": { "amount_cents": 495, "currency": "usd", "seller_profile_id": "profile_example", "checkout_url": "https://checkout.stripe.com/c/pay/cs_example", "instruction": "send the user to checkout_url; when they finish, call update_delivery (update_type payment, checkout_completed true)" }, "released_delivery_ids": [] } ``` Keep the order in the user's own name and use the customer's own email. Calling `reserve_delivery` again on the same delivery while the hold is live keeps that hold and its end time; after the hold lapses, it takes a new 15-minute hold. A reserve for a different store releases the user's earlier hold that has no store order yet; its id appears in `released_delivery_ids` and `user_message` adds "An earlier Autolane hold was released." **Declines.** The delivery becomes `declined` and the hold is released: `store_not_supported`, `disqualifying_requirement`, `app_only_check_in`, `customer_unreachable` (the number has opted out of texts) or `no_capacity`. `next_step` is "Do not submit the retailer order for Autolane delivery; tell the user why". **Errors.** | `error.code` | `error.message` | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `not_found` | `Unknown delivery_id` | | `invalid_input` | `contact.phone must be the phone verified when the account was linked, or be omitted` | | `invalid_input` | `Consent and terms must be accepted at versions 2026-09-27 and 2026-09-27` | | `quote_expired` | `The quote has expired; call check_delivery again` | | `invalid_state` | `Not allowed while` and the state, when the delivery is not `quoted` or `held` | | `payment_failed` | `The payment credential was declined; obtain a new one and send it with update_delivery (update_type payment)`. The envelope still carries the whole reserve payload, so you keep the pickup profile. A token already used on another delivery is also refused. | | `internal_error` | `Could not confirm capacity for this pickup; retry with the same idempotency_key`, or `The payment provider did not answer` | | `idempotency_conflict`, `rate_limited` | See [Error codes](#error-codes). | ### confirm\_delivery Tells Autolane the store order is placed, and books the delivery. Call it right after you submit the store order. | Field | Type | Required | Rule | | ---------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `delivery_id` | string | Yes | | | `retailer_order_number` | string | Yes | 1 to 64 characters. | | `name_on_order` | string | Yes | 1 to 200 characters. | | `fulfillment_method` | string | Yes | `curbside` | | `items_summary` | array | Yes | 1 to 100 items, every item with its quantity. | | `retailer_confirmation_text` | string | Yes | The confirmation page or email text, up to 20,000 characters. May be empty. | | `confirmed_ready_time` | time | No | The ready time the store gave. More than 15 minutes in the past answers `invalid_input`; more than 90 minutes ahead declines `timing_not_feasible`. Ignored at stores where the kitchen starts on check-in. | | `order_subtotal` | number | No | 0 to 100,000, in dollars. | | `check_in_method` | string | No | `email_link`, `order_page`, `call_store`, `unknown` or `app_only`. | | `check_in_link` | string | No | Up to 2,000 characters. Must be `https` on a domain Autolane has on record for the store. | | `pickup_code` | object | No | `text`, up to 64 characters: a pickup code, order code or barcode number the store shows. Text only; images are not accepted yet. | | `polls_after_turn` | boolean | No | Default `true`. Send `false` if you cannot keep polling after the user's turn ends; Autolane's supervisor then checks in by hand on arrival. | | `payment` | object | No | `shared_payment_token`, 1 to 200 characters. | | `idempotency_key` | string | Yes | 8 to 128 characters. | ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "retailer_order_number": "A1042", "name_on_order": "Sam Rivera", "fulfillment_method": "curbside", "items_summary": [ { "name": "Chicken tacos", "quantity": 2, "modifiers": ["no onions"] }, { "name": "Horchata", "quantity": 1 } ], "retailer_confirmation_text": "Thanks, Sam! Order A1042 is in. Check in when you arrive for curbside pickup.", "order_subtotal": 18.5, "check_in_method": "order_page", "pickup_code": { "text": "A1042" }, "polls_after_turn": true, "idempotency_key": "confirm-7f3a9c21" } ``` **Response.** `state: "booked"` with: | Field | Meaning | | ----------------------------- | ------------------------------------------------------------------------------------------------- | | `retailer_order_number` | As sent. | | `payment_status`, `fee_cents` | See [Payment](#payment). | | `sms_reachable` | `false` when the user's number has opted out of texts. Share `tracking_url` with the user then. | | `planned_pickup_time` | When the vehicle plans to collect the order. | | `estimated_delivery_window` | `start` and `end`, 15 minutes apart. | | `tracking_url` | A status-only link for the user. See [Texts the customer receives](#texts-the-customer-receives). | ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "state": "booked", "next_step": "Poll get_delivery_status; the vehicle collects the order at planned_pickup_time", "user_message": "Your Autolane delivery is booked; the vehicle collects the order at 6:45 PM.", "retailer_name": "Example Taqueria", "store_address": { "street": "123 Example St" }, "retailer_order_number": "A1042", "payment_status": "authorized", "fee_cents": 495, "sms_reachable": true, "planned_pickup_time": "2026-10-06T23:45:00.000Z", "estimated_delivery_window": { "start": "2026-10-07T00:00:00.000Z", "end": "2026-10-07T00:15:00.000Z" }, "tracking_url": "https://autola.ne/t/tok_example" } ``` A confirm on a delivery that is already booked returns the booking again. **Declines.** The store order already exists, so `next_step` is "Tell the user the order is ready for regular curbside pickup at the store; Autolane cannot deliver it". Reasons: `customer_unreachable` (no reserve ran), `store_not_supported`, `timing_not_feasible`, `no_capacity` and `disqualifying_requirement` (a restricted item). **Errors.** | `error.code` | `error.message` | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `not_found` | `Unknown delivery_id` | | `invalid_input` | `check_in_link must be https on one of the store's check-in domains`, `confirmed_ready_time is too far in the past`, or `pickup_code images are not accepted yet` | | `invalid_state` | `The delivery was already closed; Autolane is arranging the booking for this order` (the delivery was declined or cancelled; Autolane records the order and arranges it), or `Not allowed while` and the state | | `payment_failed` | `No payment is authorized for this delivery; obtain a credential and send it with update_delivery (update_type payment)`. Autolane records the order and, if the hold is live, extends it by 10 minutes. Send a credential, then call `confirm_delivery` again. | | `internal_error` | `Could not confirm capacity for this pickup; retry with the same idempotency_key`, or `The payment provider did not answer` | | `idempotency_conflict`, `rate_limited` | See [Error codes](#error-codes). | ### update\_delivery Reports payment, or a change after the reserve. Send `delivery_id`, `update_type`, `details` and `idempotency_key`. Autolane checks `details` against the schema for the type; a mismatch answers `invalid_input` with `details for` and the type, then the first problem found. States in this table are stored states: `booked` covers what `get_delivery_status` shows as `booked`, `picked_up`, `delivered` and `failed`. | `update_type` | `details` | Allowed when | Effect | | ------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `payment` | `shared_payment_token` (1 to 200 characters), or `checkout_completed: true` | Any state but `declined` or `cancelled`, including after booking and after delivery | Starts a payment attempt with the token, or checks the hosted Checkout session. Authorized: `payment_status` becomes `authorized`. Not authorized: `payment_failed`, `The payment was not authorized; obtain a new credential and send it with update_delivery (update_type payment)`. Never refused because the hold lapsed. | | `check_in_link` | `check_in_method` (required), `url` (up to 2,000 characters), `instructions_text` (up to 2,000 characters) | `held`, `booked` | Saves the store's check-in method, link and instructions. `url` must be `https` on a domain Autolane has on record for the store, else `invalid_input`, `check_in_link url must be https on one of the store's check-in domains`. | | `checked_in` | `method` (required), `stall_id` (up to 16 characters) | `booked`, with a pickup | Records that the user checked the order in with the store. Call it only when the user says they did so themselves, never just because the vehicle arrived. | | `requirement_found` | `requirements` (1 to 10 of the pickup requirement values), `notes` (up to 1,000 characters) | `held`, `booked` | Screens the new requirements. A disqualifier while `held` declines (`disqualifying_requirement` or `app_only_check_in`) and releases the hold. While `booked`, Autolane cancels the delivery and the order stays at the store. | | `ready_time_change` | `new_ready_time` (time) | `held`, `booked` | Moves the pickup. More than 15 minutes in the past: `invalid_input`, `new_ready_time is too far in the past`. More than 90 minutes ahead: `timing_not_feasible`, `new_ready_time is beyond the hold horizon`. No vehicle fits: `timing_not_feasible`, `No vehicle fits the new time`. | | `order_modified` | `items_summary` (1 to 100 items), `order_subtotal` (0 to 100,000) | `held`, `booked`, before the vehicle collects the order | Replaces the items and screens them again. A restricted item on a booked delivery puts it on hold for Autolane review; it is not cancelled. | | `retailer_message` | `text` (1 to 2,000 characters), `channel` (1 to 32 characters) | `quoted`, `held`, `booked` | Passes a store message to Autolane's dispatch. Autolane keeps the last 20. | | `order_failed` | `reason_text` (up to 500 characters) | `quoted`, `held`, `expired`, `booked`, before the vehicle collects the order | The store order did not go through. Cancels the delivery and releases the hold. Do not also call `cancel_delivery`. | | `pickup_code` | `text` (up to 64 characters) | `held`, `booked` | Saves the pickup code for the vehicle. Text only: an image answers `invalid_input`, `pickup_code images are not accepted yet`. No text answers `pickup_code needs text or an image`. | The `method` and `check_in_method` values are `email_link`, `order_page`, `call_store`, `unknown` and `app_only`. ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "update_type": "payment", "details": { "checkout_completed": true }, "idempotency_key": "update-pay-7f3a9c21" } ``` ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "update_type": "ready_time_change", "details": { "new_ready_time": "2026-10-07T00:05:00.000Z" }, "idempotency_key": "update-ready-7f3a9c21" } ``` **Response.** The common envelope (`delivery_id`, `state`, `next_step`, `user_message`, `retailer_name`, `store_address`, `retailer_order_number` when known, `payment_status`, `fee_cents`) with nothing type-specific. `user_message` by type: | `update_type` | `user_message` | | ------------------- | ---------------------------------------------------------------------------------------------------- | | `payment` | `The delivery fee is authorized.` | | `check_in_link` | `Autolane saved the check-in details.` | | `checked_in` | `Autolane noted the check-in.` | | `requirement_found` | `Autolane recorded the pickup requirements.` | | `ready_time_change` | `Autolane moved the pickup to` and the new time, for example `Autolane moved the pickup to 7:05 PM.` | | `order_modified` | `Autolane updated the order details.` | | `retailer_message` | `Autolane passed the message to dispatch.` | | `order_failed` | `Your Autolane delivery is cancelled because the order did not go through.` | | `pickup_code` | `Autolane saved the pickup code.` | **Declines.** Only `requirement_found` while `held` declines, with `disqualifying_requirement` or `app_only_check_in`. **Errors.** `invalid_input`; `not_found`; `hold_expired` (any type but `payment`, when the hold lapsed and no store order was submitted); `invalid_state` (including `The order is already collected`); `timing_not_feasible`; `payment_failed`; `internal_error` (including `The payment provider did not answer`); `idempotency_conflict`; `rate_limited`. ### get\_delivery\_status Reads the delivery's state. Send only `delivery_id`; there is no idempotency key. ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw" } ``` **Response.** | Field | Meaning | | ----------------------------- | ------------------------------------------------------------------------------------- | | `state` | See [Delivery states](#delivery-states). | | `status_detail` | Present once booked, otherwise `null`. | | `stall_id` | The store stall the vehicle waits in, when known, otherwise `null`. | | `eta_pickup`, `eta_dropoff` | Estimated times, or `null`. | | `vehicle` | `description`, for example `Grey Tesla Model Y`. | | `payment_status`, `fee_cents` | After the reserve. | | `fee_charged` | `true` only once Autolane has captured the fee. | | `sms_reachable` | After the reserve. `false` when the number has opted out of texts. | | `cached` | `true` when this read came inside the fresh-read allowance (see [Polling](#polling)). | | `tracking_url` | A fresh status-only link on every read, or `null` before booking. | ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "state": "booked", "next_step": "Poll again in 2 minutes", "user_message": "An Autolane vehicle is assigned.", "retailer_name": "Example Taqueria", "store_address": { "street": "123 Example St" }, "retailer_order_number": "A1042", "payment_status": "authorized", "fee_cents": 495, "sms_reachable": true, "status_detail": "vehicle_assigned", "stall_id": null, "eta_pickup": "2026-10-06T23:45:00.000Z", "eta_dropoff": "2026-10-07T00:05:00.000Z", "vehicle": { "description": "Grey Tesla Model Y" }, "fee_charged": false, "cached": false, "tracking_url": "https://autola.ne/t/tok_example" } ``` **Errors.** `not_found`, `rate_limited`, `internal_error`. ### cancel\_delivery Cancels the delivery. Allowed until the vehicle has collected the order. Not needed after `update_delivery` with `order_failed`. | Field | Type | Required | Rule | | ----------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `delivery_id` | string | Yes | | | `reason` | string | Yes | `user_cancelled`, `retailer_order_failed`, `retailer_cancelled`, `disqualifier_at_checkout`, `hold_expired` or `other`. | | `reason_text` | string | No | Up to 500 characters. | | `retailer_order_placed` | string | Yes | Whether the store order exists: the strings `"true"`, `"false"` or `"unknown"`. These are strings, not booleans; `true` without quotes fails the schema. Send `"unknown"` when you cannot tell, and Autolane's team checks. | | `idempotency_key` | string | Yes | 8 to 128 characters. | ```json { "delivery_id": "dlv_4fG7hJ2kL9mN3pQ5rS8tVw", "reason": "user_cancelled", "reason_text": "The user changed their mind", "retailer_order_placed": "false", "idempotency_key": "cancel-7f3a9c21" } ``` **Response.** `state: "cancelled"`, `payment_status`, `fee_cents` and `fee_charged`. `next_step` is "Tell the user the delivery is cancelled". `user_message` is "Your Autolane delivery is cancelled and no fee was charged." (or "Your Autolane delivery is cancelled and the delivery fee was charged." when `fee_charged` is `true`). A cancel before the vehicle collects the order releases the fee authorization. **Errors.** `not_found`; `invalid_state` (`The order is already collected`, or `Not allowed while` and the state on a declined or cancelled delivery); `idempotency_conflict`; `rate_limited`; `internal_error`. ## Responses ### Envelope Every accepted call returns this object as `structuredContent` and as JSON text. | Field | Type | Present | Meaning | | ----------------------- | -------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `delivery_id` | string or null | Always | `null` only on a refusal before a delivery exists. | | `state` | string or null | Always | See [Delivery states](#delivery-states). | | `next_step` | string | Always | The exact call to make next. | | `user_message` | string | Always | One sentence you may show the user. | | `error` | object | Refusals only | `code` (below) and `message`, a short sentence written by Autolane. | | `retailer_name` | string or null | Always | The store. | | `store_address` | object | Always | `street` (string or null). | | `retailer_order_number` | string | Once known | | | `payment_status` | string | After the reserve | See [Payment](#payment). | | `fee_cents` | integer | With `payment_status` | `495` today. | | `sms_reachable` | boolean | After the reserve (status) or booking (confirm) | `false` when the number has opted out of texts. | | `stall_id` | string | When known | | | Tool fields | varies | Per tool | Such as `pickup_profile`, `payment`, `planned_pickup_time`, `status_detail`, `fee_charged`, `cached`, `tracking_url`, `decline_reason`, `serviceable`. | A refusal repeats the delivery's `delivery_id`, `state`, `payment_status`, `retailer_name`, `store_address`, `retailer_order_number` and `fee_cents` when the tool had loaded them. ### Error codes | `error.code` | Meaning | What to do | Retry with the same key? | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | `invalid_input` | A field is wrong for this call: the phone rule, the consent version, a link domain, a ready time, an image, or the shape of `details`. | Fix the field named in `error.message` and call again. | Yes. Refusals are not stored. | | `invalid_state` | The delivery's state does not allow this call. | Follow `next_step`, often `Call get_delivery_status`. | Only after the state changes. | | `quote_expired` | The 30-minute quote lapsed before the reserve. | `Call check_delivery again for a fresh quote`. If a store order exists, call `confirm_delivery` and Autolane arranges the booking. | No. Start again with `check_delivery`. | | `hold_expired` | An update other than `payment` on a lapsed hold, with no store order submitted. | Call `reserve_delivery` again, then send the payment credential if you have not. | No. Reserve again first. | | `payment_failed` | The credential was declined, was already used on another delivery, or no payment is authorized at confirm. | Get a new credential and send it with `update_delivery` (`payment`). | Not with the same credential. Send a new one. | | `timing_not_feasible` | The ready time is beyond the 90-minute horizon, or no vehicle fits the new time. | Keep the current time, or pick a time inside `pickup_windows`. | Only with a different time. | | `idempotency_conflict` | The key was used before with different arguments. | Send a new key with the changed request, or repeat the original request exactly. | No. | | `not_found` | Unknown `delivery_id`, or one that belongs to another user. | Check the id; if it is unknown, start again with `check_delivery`. | No. | | `rate_limited` | A rate limit is full. | `Wait and retry`. | Yes, after waiting. | | `internal_error` | An unexpected failure, a map or payment provider outage, or the same key still in flight. The MCP result has `isError: true`. | `Retry with the same idempotency_key`. | Yes. A call that stopped part way continues from where it stopped. | Each code has a fixed `user_message`: | `error.code` | `user_message` | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_input` | `Autolane needs corrected details for this delivery.` | | `invalid_state` | Depends on the state, for example `Your Autolane delivery is booked.` or `This Autolane delivery is no longer active.` | | `quote_expired` | `The delivery quote expired; Autolane can quote again.` | | `hold_expired` | `The vehicle reservation expired; Autolane can reserve again.` | | `payment_failed` | `The payment for the delivery fee was declined; another payment method is needed.` | | `timing_not_feasible` | `No Autolane vehicle can make that time.` | | `idempotency_conflict` | `Autolane received a changed request under a reused key.` | | `not_found` | `Autolane does not recognise this delivery.` | | `rate_limited` | `Autolane is busy; please try again in a moment.` | | `internal_error` | `Autolane hit a problem and is retrying.` | ### Decline reasons A decline is not an error. The call succeeds with `state: "declined"` and a `decline_reason`, and a retry with the same key returns it again. | `decline_reason` | From | `user_message` | | --------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `no_curbside` | check | `This store does not offer curbside pickup, so Autolane cannot deliver it.` | | `store_not_supported` | check, reserve, confirm | `Autolane does not serve this store yet.` | | `disqualifying_requirement` | check, reserve, confirm, update | `This order needs an ID, signature, name check or a restricted item at pickup, which Autolane cannot provide.` | | `out_of_zone` | check | `The delivery address is outside Autolane's service area.` | | `outside_service_hours` | check | `Autolane is closed for the requested time.` | | `no_capacity` | check, reserve, confirm | `No Autolane vehicle is available for this time.` | | `timing_not_feasible` | check, confirm | `Autolane cannot meet the requested pickup time.` | | `app_only_check_in` | reserve, update | `This store checks in through its own app, which Autolane cannot do.` | | `customer_unreachable` | reserve, confirm | `Autolane cannot text this phone number, so it cannot deliver.` | When a store order was already submitted, `disqualifying_requirement` and `no_capacity` add " Your order is ready for regular curbside pickup at the store." ## Delivery states | `state` | Meaning | `user_message` on a status read (no store order / store order submitted) | | ----------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `quoted` | A serviceable check; no vehicle held. Reads `expired` once past `quote_expires_at` with no store order. | `Autolane's quote is active.` / `Autolane is arranging your delivery.` | | `held` | A vehicle is reserved until `hold_expires_at`. Reads `quoted` once the hold lapses. | `An Autolane vehicle is reserved.` / `Autolane is arranging your delivery.` | | `booked` | The store order is recorded and Autolane has scheduled the pickup. | By `status_detail` | | `picked_up` | The vehicle has the order. | By `status_detail` | | `delivered` | The order was delivered. | `The order was delivered.` | | `failed` | Autolane could not complete the delivery. | `Autolane could not complete the delivery; contact Autolane support.` | | `declined` | Autolane declined at check, reserve, confirm or update. | `This Autolane delivery is not active.` / `Autolane is arranging your delivery.` | | `expired` | The quote lapsed with no store order. | `The Autolane quote expired.` / `Autolane is arranging your delivery.` | | `cancelled` | The agent, a failed store order or Autolane ended the delivery. | `This Autolane delivery is not active.` / `Autolane is arranging your delivery.` | ### status\_detail | `status_detail` | Meaning (`user_message`) | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `scheduled` | `Autolane will collect the order at the planned time.` | | `vehicle_assigned` | `An Autolane vehicle is assigned.` | | `en_route_to_store` | `The Autolane vehicle is on its way to the store.` | | `arrived_at_store` | `The Autolane vehicle is at the store.` With a stall, when known: `The Autolane vehicle is at the store in stall` and the stall id. | | `checked_in` | `The store is preparing the order for the Autolane vehicle.` | | `order_collected` | `The Autolane vehicle has the order.` | | `en_route_to_customer` | `The order is on its way.` | | `arrived_at_customer` | `The Autolane vehicle is outside; open the trunk from the link Autolane texted.` | | `delivered` | `The order was delivered.` | | `exception` | `Autolane could not complete the delivery; contact Autolane support.` | When `sms_reachable` is `false`, the messages from `scheduled` through `en_route_to_customer` add " Autolane cannot text this phone; share the tracking link." The `arrived_at_customer` message does not change and still points to the texted link, which the customer did not get. In that case, do not repeat it: tell the user the vehicle is outside and Autolane's dispatch will call them to hand over the order. When the customer replies STOP, Autolane alerts dispatch to call them before the vehicle arrives. When the vehicle reaches the store, do not check in on the store's order page, email link or confirmation text yourself. Autolane's supervisor checks the order in with the store by phone. Call `update_delivery` with `checked_in` only if the user tells you they already checked the order in themselves. ### Polling `next_step` on every status read says when to poll again: | `status_detail` | Poll | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scheduled`, `vehicle_assigned` | Every 2 minutes | | `en_route_to_store` | Every 30 seconds | | `arrived_at_store` | Every 30 seconds | | `checked_in` | Every minute | | `order_collected`, `en_route_to_customer` | Every 2 minutes. Tell the user the order is on its way. | | `arrived_at_customer` | Stop. Tell the user the vehicle is outside and the trunk opens from the link Autolane texted them. If `sms_reachable` is `false`, say instead that Autolane's dispatch will call them to hand over the order. | | `delivered` | Stop. No further calls. | | `exception` | Stop. Tell the user to contact Autolane support through `tracking_url`. | Each delivery gets one fresh read every 120 seconds, or every 30 seconds from the time the vehicle leaves for the store until it leaves the store with the order. A read inside that allowance is not refused: it returns the last stored result with `cached: true` and a fresh `tracking_url`. ## Payment **The fee.** Autolane charges a delivery fee, \$4.95 today. Every check quotes it (`price.amount`, in dollars), and every response after the reserve carries it as `fee_cents`. Autolane charges the fee itself, separately from the store order; the user pays the store for the food as usual. **The payment request.** In production, `reserve_delivery` returns a `payment` block: | Field | Meaning | | ------------------- | ---------------------------------------------------------------------------------- | | `amount_cents` | The fee in cents, `495` today. | | `currency` | `usd` | | `seller_profile_id` | Autolane's Stripe profile. Scope a shared payment token to it. | | `checkout_url` | A hosted Stripe Checkout link for the fee, present while no payment is authorized. | | `instruction` | What to do next, in one sentence. | Get the user's approval for the fee, then pay one of two ways, before you submit the store order: 1. **Hosted Checkout.** Send the user to `checkout_url`. When they finish, call `update_delivery` with `update_type: "payment"` and `details: { "checkout_completed": true }`. Autolane checks the session and answers `payment_status: "authorized"`, or `payment_failed`. 2. **Shared payment token.** Get a Stripe shared payment token scoped to `seller_profile_id` and send it with `update_delivery` (`update_type: "payment"`, `details: { "shared_payment_token": "..." }`), or on `reserve_delivery` or `confirm_delivery` as `payment.shared_payment_token`. When a payment is already authorized, the `payment` block has no `checkout_url` and `instruction` says no further payment step is needed. **Authorize, then capture.** Autolane authorizes the fee when you send the credential and captures it after the order is delivered. If the delivery ends any other way (cancelled, declined, or not completed), Autolane releases the authorization and charges nothing. The terms let Autolane keep the fee when the store cannot hand over the order, the order holds items Autolane cannot carry, or the user cancels after the vehicle collects it; Autolane does not charge it in those cases today. **Declines.** A declined payment answers `payment_failed`. Get a new credential and send it the same way; each new credential starts a new attempt. Resending the same token returns the same attempt. A token already used on another delivery is refused. If the hold has lapsed, call `reserve_delivery` again first, then send the credential. **Store order submitted without a payment.** Never submit the store order before the fee is authorized. If it happens, Autolane still books the delivery from the recorded order. Status then shows `payment_status: "unpaid"` (or `"failed"` after a decline), and `next_step` asks for a credential. A booked delivery still accepts one, before or after delivery. | `payment_status` | Meaning | | ---------------- | ----------------------------------------------------------------------------------------------------- | | `pending` | A payment attempt is open: a Checkout session waiting for the user, or a credential being authorized. | | `authorized` | The fee is authorized. No further payment step is needed. | | `unpaid` | No payment is open or authorized. | | `failed` | The newest attempt was declined. | | `captured` | Autolane charged the fee after delivery. `fee_charged` is `true`. | | `released` | Autolane released the authorization. Nothing was charged. | | `refunded` | Autolane refunded a charged fee. | | `written_off` | Autolane will not collect the fee. | ## Limits **Tool rate limits.** A call over a limit answers `rate_limited` ("Wait and retry"). | Limit | Value | | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | All tools, all users of the connector together | 600 calls per 60 seconds | | All tools, per linked user | 60 calls per 60 seconds | | `check_delivery`, all users together | 120 calls per 60 seconds | | `reserve_delivery`, per phone (the `contact.phone` sent, or the linked user when none is sent) | 10 calls per hour | | `get_delivery_status` fresh reads, per delivery | 1 per 120 seconds, or 1 per 30 seconds from the time the vehicle leaves for the store until it leaves the store with the order. Extra reads return `cached: true`, not an error. | **OAuth rate limits.** Over a limit, the endpoint answers `429` with no `Retry-After` header. | Endpoint | Limit | | ---------------- | -------------------------------------------------------------- | | Register | 20 per hour per address | | Authorize pages | 60 per minute per address | | SMS codes sent | 5 per hour per number, and 20 per hour per address | | SMS code checks | 10 per 10 minutes per number | | Token and revoke | 120 per minute per `client_id`, and 240 per minute per address | **OAuth body sizes.** Form bodies (authorize, token, revoke) up to 16,384 bytes. JSON bodies (register) up to 65,536 bytes. **Main input bounds.** | Input | Bound | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | | `idempotency_key` | 8 to 128 characters | | `retailer_name` | 1 to 200 characters | | Address `street`, `city` | 1 to 255 characters | | Address `unit` | Up to 64 characters | | Address `state` | 1 to 64 characters | | Address `zip` | 1 to 20 characters | | `store_url`, `check_in_link`, `url` | Up to 2,000 characters | | `dropoff_notes`, `reason_text` | Up to 500 characters | | `pickup_requirements_notes`, `requirement_found.notes` | Up to 1,000 characters | | `retailer_slots` | Up to 50 | | `customer.first_name`, `customer.last_name` | 1 to 100 characters | | `contact.email` | Up to 254 characters | | `retailer_order_number` | 1 to 64 characters | | `name_on_order` | 1 to 200 characters | | `items_summary` | 1 to 100 items; name 1 to 200 characters; quantity 1 to 999; up to 20 modifiers of up to 100 characters | | `retailer_confirmation_text` | Up to 20,000 characters | | `order_subtotal` | 0 to 100,000 | | `pickup_code.text` | Up to 64 characters | | `instructions_text`, `retailer_message.text` | Up to 2,000 characters | | `retailer_message.channel` | 1 to 32 characters | | `stall_id` | Up to 16 characters | | `shared_payment_token` | 1 to 200 characters | | Ready times | No more than 15 minutes in the past and no more than 90 minutes ahead | ## Data and privacy **What Autolane receives.** From the agent: the store's name, address and website; the drop-off address and notes; the requested timing, the store's pickup options and slots; the customer's first and last name, the linked mobile number and an optional email; the consent and terms acceptance, with versions and time; pickup requirements and notes; the check-in method, link and instructions; the store order number, name on the order, items (names, quantities, modifiers), subtotal, confirmation text, ready time, store messages and pickup code; and any cancel reason. From sign-in: the mobile number and when it was last verified. Autolane stores authorization codes and tokens only as hashes. **Third parties.** | Party | What it receives | Why | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | Google Maps Platform | The store's name and address; the drop-off address | Finding the store and placing the drop-off on the map | | Stripe | The fee amount, the `delivery_id`, and the shared payment token or Checkout session | Taking the delivery fee | | Twilio | The mobile number, and the text messages below | Sending sign-in codes and delivery texts | | An item-screening service | Item names and modifiers, and the pickup requirement notes, with phone numbers and emails removed. Never a name, address, phone number, store or `delivery_id`. | Screening for restricted items | Autolane never receives card numbers. Card details go to Stripe through Checkout or the shared payment token. **Retention.** The store's confirmation text and store messages are deleted 90 days after the delivery closes. Autolane keeps the delivery record, including the consent record, for support, billing and compliance. ## Texts the customer receives Autolane texts the linked mobile number. The links below point to the customer's own tracking page. **Booking.** Sent once, right after the delivery is booked, unless the number has opted out: ```text Autolane will text you updates about your {store} delivery. Track it here: {tracking link}. Reply HELP for help, STOP to opt out. Msg&data rates may apply. ``` **On the way.** Texts when the vehicle sets off for the store, when it leaves the store with the order, and when it is almost at the drop-off. **Arrival.** The arrival text carries the link that opens the trunk: ```text Your {store} delivery is here! Open the trunk from the link below and grab your order: {link}. The vehicle will wait 5 minutes. ``` If the vehicle is still waiting near the end of that time, a last-call text follows before it leaves. **Cancelled after booking.** When Autolane cannot deliver but the store order stands: ```text Autolane can't deliver your {store} order #{order number}. Your order is still ready for curbside pickup at the store. Details: {tracking link} ``` When the store order did not go through: ```text Your {store} order didn't go through, so your Autolane delivery is cancelled. You have not been charged. Details: {tracking link} ``` A cancel the user asked for sends no text. **STOP, START and HELP.** * Reply **STOP** to stop the texts. Autolane's dispatch then calls the customer about any delivery in progress. STOP does not cancel the order. After STOP, `sms_reachable` reads `false`; share `tracking_url` with the user. * Reply **START** to resume the texts. * Reply **HELP** for help. **The agent's tracking link.** The `tracking_url` that the tools return is status-only: it shows the delivery's state, times and vehicle description, with no license plate, no location and no trunk control. It lasts 24 hours, and every status read returns a fresh one. The trunk opens only from the link Autolane texts the customer; for a customer who replied STOP, dispatch calls instead. ## Support Email [support@goautolane.com](mailto:support@goautolane.com) with the `delivery_id` or the tracking link. > MCP reference for Autolane curbside delivery in Austin