Ship.com MCP Server

The Ship.com MCP server lets AI assistants and agents work with your Ship.com account: check orders, compare rates, buy labels, schedule pickups and more. This reference covers how to connect any MCP client, how authentication works, and every tool the server offers.

Overview

The Ship.com MCP server gives AI assistants, IDEs and your own code access to a Ship.com account through the Model Context Protocol. It offers tools to read and create orders, compare rates, buy and void labels, schedule carrier pickups, manage customers, send customer email and pull business reports. Every call runs under the same account rules, balances and safety checks as the Ship.com web app.

This page is the client-neutral reference. For step-by-step setup in Claude, see Connect Claude to Ship.com.

Endpointhttps://app.ship.com/mcp
TransportMCP Streamable HTTP. Send each JSON-RPC 2.0 request as a POST body; every JSON-RPC reply is a single application/json object (notifications get HTTP 202 with no body). The server doesn't stream and never sends server-sent events.
Protocol versions2025-11-25 (default) and 2025-06-18
Methodsinitialize, ping, tools/list, tools/call
Server nameship-com

Who can use it

MCP access is included with every Ship.com plan, including the free trial; there's no separate MCP plan. You need a Ship.com login and one of the two credentials below. A few tools also depend on your plan: carrier pickup scheduling, batch label buying, business reports and customer email each need a plan that includes that feature. Those tools are still listed, and a call explains plainly when your plan doesn't include them. See Plan features.

Two ways to connect

Access tokenOAuth sign-in
Works withAny MCP client that can send a request header: Claude Code, the Claude API, Cursor, VS Code, Windsurf, scripts and automation platformsClaude's web, desktop and mobile apps (custom connector or the Claude directory), and Claude Code when it signs in instead of using a header
How it authenticatesAuthorization: Bearer header with your Ship.com access token, copied from SettingsOAuth 2.1 with PKCE. You sign in to Ship.com and approve the app
Credential lifetime180 days24-hour access tokens, renewed automatically. The app stays connected as long as it renews at least once every 180 days
Tools40, the full catalog38
Label purchasingYes: buy_label and trigger_batch_purchaseNot offered. Rates, quotes, voids, orders and pickups all work
RevokeReplace the token from SettingsDisconnect the app under Connected apps in Settings

Label purchasing. Connections made through OAuth sign-in, including the Claude directory connector and claude.ai custom connectors, don't offer label purchasing. They can still rate and quote, void labels, manage orders and customers, and book pickups. The two label-purchasing tools are offered only on connections that authenticate with a Ship.com access token. Ship.com's OAuth sign-in currently accepts Claude clients only, so other tools connect with an access token. Details are in Authentication.

Quick start

1. Get your access token.

  1. Sign in at app.ship.com, open Settings and choose the API Integration tab. You can also click MCP in the left sidebar.
  2. Find the card Connect Ship.com to your AI assistant. Under Connect from another tool, click Show my connection key. The card shows the server URL and a Header value, Authorization: Bearer followed by your token. Ship.com calls this token your connection key.
  3. For Claude Code, go to Connect from Claude instead, click Prefer Claude Code or the terminal? Use a connection key instead, then Generate connection key. The card shows a ready-to-run command with your token in it.

The card shows when the token expires ("Connection valid until ..."). Clicking either button again returns the same token while it is still valid, so it never breaks a client you've already set up. Treat the token like a password: anyone who has it can act on your account.

2. Add Ship.com to your client. Use the setup for your tool below and replace YOUR_TOKEN with the token only: the part of the card's Header value after Bearer , starting with OAUTH2.. If you paste the whole Header value (including Authorization: Bearer) into a header field, the server answers 401 invalid_token.

3. Try it. Ship an order end to end walks through a first order, from rating to pickup.

Claude Code

Run the command from your Settings card, or this one with your token filled in:

claude mcp add --transport http shipcom https://app.ship.com/mcp --header "Authorization: Bearer YOUR_TOKEN"

Keep the --header part so Claude Code uses your access token. Without it, Claude Code signs in through OAuth instead and gets the OAuth tool set (see Tools by connection type).

Add --scope user to make Ship.com available in every project. Without it, the server is added to the current project only.

Cursor

Add Ship.com to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json in a project. This example reads the token from a SHIPCOM_TOKEN environment variable so it stays out of the file:

{
  "mcpServers": {
    "shipcom": {
      "url": "https://app.ship.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SHIPCOM_TOKEN}"
      }
    }
  }
}

Set SHIPCOM_TOKEN in the environment Cursor starts from (for example in your shell profile, then restart Cursor). If it's unset, the header is empty and the server answers 401 missing_bearer_token.

VS Code

Add Ship.com to .vscode/mcp.json in your workspace, or to your user configuration with the MCP: Open User Configuration command. The input asks for the token when the server first starts, so it isn't written into the file:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "shipcom-token",
      "description": "Ship.com access token",
      "password": true
    }
  ],
  "servers": {
    "shipcom": {
      "type": "http",
      "url": "https://app.ship.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:shipcom-token}"
      }
    }
  }
}

Windsurf

Open Cascade's MCP settings and edit the raw mcp_config.json. Windsurf uses serverUrl for remote servers. As with Cursor, SHIPCOM_TOKEN must be set in the environment Windsurf starts from, or the server answers 401 missing_bearer_token:

{
  "mcpServers": {
    "shipcom": {
      "serverUrl": "https://app.ship.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SHIPCOM_TOKEN}"
      }
    }
  }
}

Any other MCP client works the same way: set the server URL to https://app.ship.com/mcp, choose the HTTP (Streamable HTTP) transport, and send the Authorization header.

Raw HTTP (curl)

The server is stateless: there is no session ID to carry between calls, and each request is authenticated on its own. Send Content-Type: application/json; including text/event-stream in Accept is harmless, since replies are always plain JSON.

export SHIPCOM_TOKEN="YOUR_TOKEN"

# 1. initialize
curl -s https://app.ship.com/mcp \
  -H "Authorization: Bearer $SHIPCOM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-script","version":"1.0"}}}'

# 2. list the tools
curl -s https://app.ship.com/mcp \
  -H "Authorization: Bearer $SHIPCOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 3. call a tool
curl -s https://app.ship.com/mcp \
  -H "Authorization: Bearer $SHIPCOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_balance","arguments":{}}}'

The initialize reply (instructions shortened):

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"ship-com","version":"1.0.0"},"instructions":"Ship.com lets you rate, purchase, and track shipping labels ..."}}

The tools/call reply. The text block carries the same payload as structuredContent, pretty-printed:

{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\n  \"balance\": 42.17, ..."}],"structuredContent":{"balance":42.17,"currency":"USD","auto_recharge_amount":25.0,"auto_recharge_enabled":true,"note":"If a label purchase drives the balance below zero, the card on file is auto-charged."},"isError":false}}

A notifications/initialized message is accepted (HTTP 202, empty body) but not required.

Authentication

Every request that carries a JSON-RPC id, including initialize, needs a bearer token in the Authorization header. Ship.com accepts two kinds: a Ship.com access token that you copy from Settings, or an OAuth access token that an app gets when you sign in and approve it. Tokens are accepted in the header only, never in the URL or the request body.

Authorization: Bearer YOUR_TOKEN

A request without a valid token gets HTTP 401 with this challenge, which MCP clients use to start OAuth discovery:

WWW-Authenticate: Bearer resource_metadata="https://app.ship.com/.well-known/oauth-protected-resource"

Access tokens

Your access token is the same credential the Ship.com REST API uses. In the MCP card in Settings it's called your connection key. The Connect Claude page calls the same credential your access code. It's a long string that begins with OAUTH2.; despite the prefix, it is a plain bearer token, not an OAuth token. Connections that use it get the full catalog of 40 tools, including label purchasing.

Get itSettings → API Integration (or MCP in the sidebar) → Show my connection key or Generate connection key. See the Quick start. You can also get it from the REST API's token endpoint, documented under Token Generation.
Lifetime180 days from when it was issued. The card shows the date as "Connection valid until ...".
ReuseWhile your token is valid, both buttons return the same token. Clicking them never replaces a working token.
After it expiresCalls get HTTP 401 expired_token. Open the card again and click either button to get a new token valid for another 180 days, then update your clients.

What replaces your token

Because MCP and the REST API share one credential, these actions end the old token immediately for both. Any MCP client still using it gets HTTP 401 invalid_token until you give it the new one:

  • The Generate API Key button further down the API Integration tab. It issues a brand-new token and refresh token.
  • Refreshing the token through the REST API's Token Refresh endpoint. It issues a new token and retires the old one.

There is no separate "revoke" action for access tokens. If a token is exposed, click Generate API Key to replace it, then update every client and REST integration that used it.

Keeping it secret

  • Anyone with your token can act on your account, including buying labels from your postage balance. Treat it like a password.
  • Keep it out of source control. Use an environment variable or your client's secret prompt, as in the Cursor and VS Code examples.
  • Call the server from your own machine or backend. The endpoint refuses browser requests from other websites, so don't put the token in web-page code.

OAuth 2.1 sign-in

With OAuth, the person signs in to Ship.com and approves the app, and the app never sees their password. Ship.com is both the resource server and the authorization server. It supports the authorization code flow with PKCE (S256) for public clients, with no client secret. Connections authorized this way get 38 tools; label purchasing isn't offered (see Tools by connection type).

Supported clients. Sign-in currently accepts Claude clients only: Claude's web, desktop and mobile apps, the Claude directory connector, and Claude Code. Other tools, even ones that support MCP OAuth, can't complete sign-in and should connect with an access token. Setup steps for Claude are on Connect Claude to Ship.com.

Discovery

An unauthenticated request to /mcp returns HTTP 401 with the resource_metadata challenge shown above. Both metadata documents are public JSON and need no token.

Protected resource metadata (RFC 9728), at https://app.ship.com/.well-known/oauth-protected-resource and also at https://app.ship.com/.well-known/oauth-protected-resource/mcp:

{
  "resource": "https://app.ship.com/mcp",
  "authorization_servers": ["https://app.ship.com"],
  "scopes_supported": ["mcp:full"],
  "bearer_methods_supported": ["header"]
}

Authorization server metadata (RFC 8414), at https://app.ship.com/.well-known/oauth-authorization-server:

{
  "issuer": "https://app.ship.com",
  "authorization_endpoint": "https://app.ship.com/oauth/authorize",
  "token_endpoint": "https://app.ship.com/oauth/token",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "client_id_metadata_document_supported": true,
  "scopes_supported": ["mcp:full"]
}

There is no registration, revocation or introspection endpoint, no JWKS and no OpenID Connect configuration.

Client registration

OptionHow it works
Pre-registered clientclient_id claude, used by Claude's hosted apps. Its redirect URIs are exactly https://claude.ai/api/mcp/auth_callback and https://claude.com/api/mcp/auth_callback.
Client ID Metadata DocumentThe client_id is an https URL whose host is exactly claude.ai or claude.com. Ship.com fetches the document directly (redirects aren't followed, 8-second timeout). It must return HTTP 200 with a JSON object whose client_id equals the URL and whose redirect_uris is a non-empty list. Documents are cached for up to 30 minutes. A registered http loopback redirect (localhost, 127.0.0.1 or [::1]) matches on any port, as long as the scheme, host, path and query match.
Dynamic Client RegistrationNot supported.

Redirect URIs must match a registered value exactly, with no wildcards.

Authorization request

Send the user's browser to GET https://app.ship.com/oauth/authorize with these query parameters:

ParameterValue
response_typecode (required)
client_idSee Client registration (required)
redirect_uriA registered redirect URI (required)
code_challengeBASE64URL(SHA-256(code_verifier)) (required)
code_challenge_methodS256 (required; plain is refused)
stateRecommended. Returned unchanged on the redirect.
scopeOptional. The only scope is mcp:full, and it's always granted.

If the user isn't signed in, Ship.com asks them to sign in first, then shows the approval screen. Allow redirects to redirect_uri?code=...&state=.... Deny redirects with error=access_denied. An unknown client_id or a redirect_uri that doesn't match shows an error page on Ship.com and doesn't redirect. Other problems redirect with error=unsupported_response_type or error=invalid_request and an error_description. The code is single-use, expires after 5 minutes, and is bound to the client_id, the exact redirect_uri and the code challenge.

Token request

POST https://app.ship.com/oauth/token with a form-encoded body (application/x-www-form-urlencoded). Clients are public: send client_id in the body and no client secret.

Exchange the authorization code:

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback
&client_id=claude
&code_verifier=CODE_VERIFIER

redirect_uri must be exactly the value used in the authorization request. code_verifier must be 43 to 128 characters from A-Z a-z 0-9 - . _ ~.

Refresh:

grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=claude

A successful response, for either grant:

{
  "access_token": "mcp_at_...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "mcp_rt_...",
  "scope": "mcp:full"
}
HTTPerrorWhen
429slow_downMore than 30 token requests a minute from one IP address. Wait for Retry-After (60 seconds).
400invalid_requestThe body isn't form-encoded, or grant_type is missing.
400invalid_clientThe client_id is missing or not accepted.
400unsupported_grant_typeAny grant other than authorization_code and refresh_token.
400invalid_grantThe code or refresh token is invalid, expired, already used or revoked; the redirect_uri doesn't match; or PKCE verification failed. The description is deliberately the same for every case.

Lifetimes and rotation

  • Access tokens last 24 hours. An expired one gets HTTP 401 expired_token on /mcp; use the refresh token.
  • Refresh tokens last 180 days and rotate on every use. Each refresh returns a new access token and a new refresh token, valid for another 180 days, and immediately retires the old pair.
  • Reuse is treated as theft. Presenting a refresh token that was already rotated, or redeeming an authorization code twice, revokes every connection that app has to the account. The user then has to sign in and approve again. Store the newest refresh token before using the new access token, and don't refresh the same token from two places at once.
  • Once a refresh token has expired, the refresh returns invalid_grant and the user must sign in again.

The approval screen names the app, shows which Ship.com account is signed in, and lists what a Ship.com connection can do. The list is the same for every app and describes everything a Ship.com connection can include; the tools an OAuth connection actually receives are listed under Tools by connection type. The user chooses Allow or Deny.

Approved apps appear under Connected apps on Settings → API Integration, with when they were last used (the connected date shown updates each time the app renews its sign-in). Disconnect revokes that app's access right away: its tokens stop working (HTTP 401 invalid_token) and it must be approved again to reconnect. There is no public token revocation endpoint; disconnecting in Settings, or asking Ship.com support, is how access is withdrawn.

Tools by connection type

ConnectionToolsNot offered
Access token40None
OAuth sign-in38buy_label, trigger_batch_purchase

A tool that isn't offered on your connection doesn't appear in tools/list, and calling it returns JSON-RPC error -32602: "The tool 'buy_label' is not available on this server. Call tools/list to see the available tools." OAuth connections keep every other tool, including rating, quoting, voiding labels, canceling orders, marking labels printed, pickups and get_batch_status. Plan features don't change the list; they're checked when a tool is called (see Plan features).

How tools behave

These rules apply to every tool. The tool reference covers each tool's parameters and results.

Result format

A tools/call that reaches a tool always returns a JSON-RPC result, even when the tool refuses or fails. The result has one of two shapes:

  • Success with data: content holds one text block with the payload as pretty-printed JSON, structuredContent holds the same payload as an object, and isError is false.
  • Message: content holds one text block with a plain-language message, with no structuredContent. Every tool error uses this shape, with isError: true.
{"jsonrpc":"2.0","id":7,"result":{"content":[{"type":"text","text":"Order 48213 was not found in this account."}],"isError":true}}

Tool errors cover bad or missing arguments, records that aren't found, plan features you don't have, business refusals such as a price above your max_cost, and carrier messages, which are passed through word for word. Arguments are checked by each tool rather than against inputSchema, so a missing argument comes back as an isError message like "order_id is required." A value of the wrong type, such as the text order_number where order_id expects an integer, returns the general unexpected-error message instead. IDs are always the integer order_id, pickup_id, batch_id or job_id, never display numbers. The messages are written to be shown to the user or acted on by the assistant. A JSON-RPC error is reserved for protocol problems; see Errors.

Records outside your account are never visible. A not-found message reads the same whether the record doesn't exist or belongs to someone else.

Preview, then confirm

Tools that spend money, send email, cancel something or accept an agreement work in two steps, controlled by a boolean confirm argument:

  1. Preview. Call the tool without confirm (or with false). Nothing is bought, charged, voided, scheduled or sent. (One exception to "nothing changes": a trigger_batch_purchase preview rates each order and saves the weight and address it used, the same as get_rates.) The result includes "preview": true, what would happen (prices, balances, recipients, affected orders) and a note on how to proceed (buy_label calls it message).
  2. Confirm. Show the preview to the user. Once they agree, repeat the same call with "confirm": true.

Ship.com keeps nothing between the two calls. Most tools re-read everything on confirm, so the outcome reflects the moment you confirm. buy_label is the exception: it charges the price sealed in the rate_id you got from get_rates. That token is signed by Ship.com and can't be edited, so the price still never comes from the client. The server doesn't require a preview before a confirm, so previewing first is up to the client. Send confirm as a JSON boolean.

register_ups is stricter. Its preview returns the full UPS Technology Agreement and an agreement_token, and the confirm call must send that token back. The user must read and accept the agreement themselves; an assistant should never accept it on their behalf.

Each tool in tools/list carries annotations with a title and readOnlyHint. Write tools also carry destructiveHint. It is true for every write tool except create_order, bulk_create_orders and sync_platform_orders, which only add new records. mark_labels_printed counts as destructive because confirming it emails your customers. MCP clients use these hints to decide when to ask the user before running a tool. get_rates is marked read-only, but it can save the weight you pass and address-check results on the order.

Money safety

Only two tools spend money, buy_label and trigger_batch_purchase, and both are offered on access-token connections only. Here is where the money comes from:

  • Postage balance first. Labels are paid from your Ship.com postage balance. buy_label adds a $0.30 pay-as-you-go fee per label when your auto-recharge amount is $0.01 or less. Shipping insurance, when on, is charged on top of postage.
  • Auto-recharge. If your balance can't cover postage and insurance, what happens depends on your auto-recharge amount. Above $0.01, your payment method on file is charged the larger of that amount and the shortfall before the label is created. At $0.01 (pay as you go), it's charged the shortfall plus the $0.30 fee, at least $1. At $0, the purchase fails. Those funds stay on your balance even if the purchase then fails. A batch tops up once, before any label is bought.
  • Previews show the cost. The buy_label preview returns total_charge, current_balance, balance_after and auto_recharge_would_fire. The batch preview returns per-order prices and a postage total_cost.
  • Price ceilings. Pass max_cost to buy_label or max_total to trigger_batch_purchase. A price above the ceiling is refused and nothing is charged. Both ceilings cover postage only, not fees or insurance.
  • No double buys. If an order already has a label, buy_label returns that label and charges nothing. Its response still reads purchased: true, so check get_label first when you're not sure.
  • Batches run in the background. A confirmed batch is queued, and its labels are usually bought within about a minute. Follow it with get_batch_status. Only one batch can run at a time.
  • Refunds go to your postage balance. Voiding a label (cancel_shipping_label, or cancel_order on an unshipped order) credits your postage balance once the carrier accepts the void, typically within about 14 days, if the package was never scanned. Refunds are credited to your postage balance, not to a card.
  • Everything else is free. Pickups booked with schedule_pickup cost nothing, and redeem_referral_credit moves referral credit into postage without charging a card. To add funds or change auto-recharge, use the Ship.com web app.

Emails sent with send_customer_email and send_mass_marketing_email are real and can't be recalled, so treat them with the same care.

Plan features

Every tool is listed on every plan. Some check a plan feature when called and refuse with an isError message if your plan doesn't include it (the email tools still preview and report the result in limit_check):

FeatureToolsIncluded onMessage without it
Carrier pickupschedule_pickupCurrently Growth, Executive, Ultra"Your current Ship.com plan does not include carrier pickup scheduling. Upgrade the subscription to use this tool."
Batch shippingtrigger_batch_purchase, get_batch_statusCurrently Growth, Executive, Ultra"Your current Ship.com plan does not include batch label purchasing. Upgrade the subscription to use this tool."
Business reportingget_shipping_report, get_tax_report, get_shipping_analyticsCurrently Executive, Ultra"Your current Ship.com plan does not include business reporting. Upgrade the subscription to use this tool."
Marketing emailsend_customer_email, send_mass_marketing_emailCurrently Growth and Executive (monthly allowance), Ultra (unlimited); trials have a separate cap. See Marketing-email allowanceThe preview's limit_check shows allowed: false and reason: "no_entitlement"; confirming is refused with "Marketing email campaigns are not included with your current subscription."

Plan contents can change; get_subscription_info shows what your plan includes today.

Features follow the plan currently selected on your account. An account with no plan selected yet, or on an older plan that's no longer sold, is treated as not including them. Call get_subscription_info to check: tier.features shows carrier_pickup, batch and business_reporting. Pickup cancellation, pickup lookups and every other tool have no plan requirement. Email allowances are covered under Marketing-email allowance.

Label purchases also follow your plan's monthly shipment limit, if it has one. Past the limit, buy_label returns "Label purchase failed — Limit reached: " followed by the limit.

Errors

Errors come from three layers. Authentication, origin and rate-limit problems are plain HTTP responses with a JSON body {"error": "...", "message": "..."}, not JSON-RPC. Every 401 also carries the WWW-Authenticate challenge described under Authentication.

HTTPerrorMeaning
401missing_bearer_tokenNo Authorization: Bearer header, or an empty one.
401invalid_tokenThe token isn't recognized: it was replaced, or the app was disconnected. Also returned when the token's account no longer exists, and when the header value is malformed, for example Bearer Authorization: Bearer ....
401expired_tokenThe token has expired. Refresh an OAuth token, or get a new access token from Settings.
401invalid_informationThe request couldn't be authorized. Contact help@ship.com if you believe this is a mistake.
403forbidden_originThe request came from a browser page on a website Ship.com doesn't allow. Requests without an Origin header, such as server-side calls, aren't affected.
429rate_limitedMore than 60 requests in a minute for this token. See Rate limits.
405GET on /mcp (plain-text body). Only POST is supported, and there is no server-sent event stream.
202Not an error: a notification (a message with no id) was accepted. It is never answered.

Protocol problems return HTTP 200 with a JSON-RPC error object holding code and message:

CodeMeaning
-32700The body is empty or isn't valid JSON.
-32600Invalid request: the body isn't a single JSON object (batch arrays aren't supported), or method is missing.
-32601Unknown method. Supported methods are initialize, ping, tools/list and tools/call; names are case-sensitive.
-32602tools/call has no tool name, names a tool that doesn't exist (names are case-sensitive), or names a tool not offered on this connection.
-32603An unexpected error on Ship.com's side. Try again, and contact help@ship.com if it continues.

Everything that happens inside a tool, including plan refusals and failures, is a result with isError: true (see Result format). If a tool hits an unexpected error, its message says whether any work may have been saved. Re-read the current state, for example with get_order, get_label or get_balance, before retrying anything that spends money.

Rate limits

  • MCP endpoint: 60 requests per minute per token. The window resets at the start of each minute (UTC). Every authenticated request counts, including initialize and tools/list; notifications don't. Over the limit you get HTTP 429 with Retry-After: 60. Each token has its own allowance, so a refreshed OAuth token starts a fresh count.
  • OAuth token endpoint: 30 requests per minute per IP address, also with Retry-After: 60.
  • Business limits are separate: your plan's monthly shipment and email allowances, and one batch at a time.

Shared conventions

  • Examples: each tool's example below is the arguments object. Send it as {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_rates","arguments":{...}}}. See Ship an order end to end.
  • Names: arguments and result fields use snake_case.
  • Types: send real JSON types: integers for IDs, booleans for confirm, numbers for money and weights.
  • Money: US dollars as plain JSON numbers.
  • Weights: ounces (weight_oz).
  • Dates: YYYY-MM-DD. Timestamps in results are UTC. Most have no time zone suffix; a few, such as ship_date from get_rates, end in Z.
  • Paging: list tools take page (from 1) and page_size (up to 50), or limit; each tool lists its defaults. tools/list returns the whole catalog in one response.
  • Order statuses: status filters take names such as ChooseShipping (ready to ship) and LabelPrinted. Some results show the display label, such as "Ready to ship", instead. See Order statuses.

Tools

Access-token connections get all 40 tools. OAuth connections get 38: everything except buy_label and trigger_batch_purchase.

Access is what a tool can do: Read-only tools change nothing; Changes data tools update your records or the carrier's; Spends money tools charge your postage balance and possibly your payment method; Sends email tools email your customers. Plan feature is the feature a call needs (see Plan features); for the email tools it applies to sending. OAuth says whether the tool is offered on OAuth sign-in connections. tools/list is always the definitive list for your connection.

ToolWhat it doesAccessPlan featureOAuth
Orders and products
list_ordersList orders, ready-to-ship by defaultRead-onlyNoneYes
get_orderFull detail for one orderRead-onlyNoneYes
get_labelLabel files and tracking for a purchased labelRead-onlyNoneYes
track_orderLatest stored tracking statusRead-onlyNoneYes
list_productsYour saved product catalogRead-onlyNoneYes
Creating and changing orders
create_orderCreate one orderChanges dataNoneYes
bulk_create_ordersCreate up to 100 ordersChanges dataNoneYes
update_orderEdit an unshipped orderChanges dataNoneYes
update_order_addressChange and validate a ship-to addressChanges dataNoneYes
cancel_orderCancel an order, voiding its label if possibleChanges dataNoneYes
cancel_shipping_labelVoid a label; refund to postage balanceChanges dataNoneYes
Rates, labels, and addresses
quote_shippingRates for a destination, no order neededRead-onlyNoneYes
get_ratesRates for an orderChanges dataNoneYes
buy_labelBuy a labelSpends moneyNoneNo
validate_addressCheck and standardize a US addressRead-onlyNoneYes
register_upsOpen or link a UPS accountChanges dataNoneYes
Pickups
mark_labels_printedMark bought labels printed so a pickup collects them; preview, then confirmChanges data, sends emailNoneYes
check_pickup_availabilityCan the carrier pick up on a date?Read-onlyNoneYes
schedule_pickupBook a free carrier pickupChanges dataCarrier pickupYes
cancel_pickupCancel a free pickupChanges dataNoneYes
list_pickupsYour recent pickupsRead-onlyNoneYes
get_pickupOne pickup and its ordersRead-onlyNoneYes
Batch shipping and platform sync
trigger_batch_purchaseBuy labels for many ordersSpends moneyBatch shippingNo
get_batch_statusProgress of a batchRead-onlyBatch shippingYes
list_connected_platformsYour connected sales platformsRead-onlyNoneYes
sync_platform_ordersImport recent Shopify or Etsy ordersChanges dataNoneYes
get_sync_statusStatus of import jobsRead-onlyNoneYes
Customers and email
list_customersSearch your address bookRead-onlyNoneYes
get_customerOne customer's full recordRead-onlyNoneYes
create_or_update_customerAdd or edit a customerChanges dataNoneYes
send_customer_emailEmail one customerSends emailMarketing emailYes
send_mass_marketing_emailEmail a customer segmentSends emailMarketing emailYes
list_lapsed_customersCustomers who haven't ordered latelyRead-onlyNoneYes
Account, credits, and reports
get_balancePostage balance and auto-rechargeRead-onlyNoneYes
get_subscription_infoYour plan, limits and featuresRead-onlyNoneYes
get_referral_infoReferral credit and referralsRead-onlyNoneYes
redeem_referral_creditMove referral credit into postageChanges dataNoneYes
get_shipping_reportShipping spend by monthRead-onlyBusiness reportingYes
get_tax_reportSales tax collectedRead-onlyBusiness reportingYes
get_shipping_analyticsVolume, carriers and destinationsRead-onlyBusiness reportingYes

Ship an order end to end

This walkthrough takes one order from Ready to ship to a booked carrier pickup. Each step is a tools/call, and the JSON shown is its arguments object. Carry the IDs forward exactly as returned. Steps 4 and 5 use buy_label, which is offered on access-token connections only (see Tools by connection type).

  1. Find the order. list_orders with {} returns orders ready to ship. Keep the order_id.
  2. Read its weight. get_order with {"order_id": 48213}. Read totals.weight_oz; if it's empty, weigh the package.
  3. Rate it. get_rates with {"order_id": 48213, "weight_oz": 14, "length": 10, "width": 8, "height": 4}. Pass the weight explicitly; the saved weight isn't used for pricing. Keep the top-level package_id, then pick one row and keep its rate_id, method and service_level.
  4. Preview the purchase. buy_label with those four values and a max_cost, without confirm. Check total_charge and balance_would_go_negative, and show them to the user.
  5. Buy. Repeat the same buy_label call with "confirm": true. Keep label_url and tracking_number. If it fails, see buy_label errors.
  6. Print the label, then mark it printed. Print label_url (and form_url, the customs form, when there is one). Then call mark_labels_printed with {"order_ids": [48213]} to preview, and again with "confirm": true. The order moves from Label purchased to Label printed, which is what a pickup collects. Your customer gets your Label Printed tracking email if you have it turned on.
  7. Check a pickup date. check_pickup_availability with {"date": "2026-10-06"}.
  8. Preview the pickup. schedule_pickup with {"date": "2026-10-06", "location": "Front Door"}. Confirm that eligible_order_ids includes your order.
  9. Book it. Repeat the call with "confirm": true and keep the pickup_id.

Step 3 as a complete request:

curl -s https://app.ship.com/mcp \
  -H "Authorization: Bearer $SHIPCOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_rates","arguments":{"order_id":48213,"weight_oz":14,"length":10,"width":8,"height":4}}}'

Step 5's request body, with the tokens shortened:

{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"buy_label","arguments":{"order_id":48213,"rate_id":"eyJhbGciOi...","package_id":"eyJhbGciOi...","method":"USPS Ground Advantage","service_level":"usps_ground_advantage","max_cost":8.00,"confirm":true}}}

If you change the order's address, weight or dimensions after step 3, rate it again before buying: the rate_id and package_id keep the values from when you rated.

Orders and products

These tools read your orders, purchased labels, tracking and product catalog. A typical flow starts with list_orders to see what's ready to ship and opens one with get_order; once a label is bought, get_label returns it and track_order follows delivery, while list_products looks up product names and prices when you build an order. All five are read-only, work on every plan and every connection type, return only your own account's data, and read what Ship.com has stored without contacting carriers or sales platforms.

list_orders

Read-only

Lists the orders in your account, newest first by order date. With no filter it returns the orders that are ready to ship, the unshipped work queue where rating and buying labels usually starts. Rows are compact; call get_order for an order's full detail.

ParameterTypeRequiredDescription
statusstringNoWhich orders to return. Omit for orders ready to ship (ChooseShipping). Send active for every order except canceled and merged ones, or one status name from the table below, such as PrintLabel, InTransit or Delivered. One status per call.
pageintegerNo1-based page number. Default 1. Values below 1 are treated as 1. A page past the end returns an empty list.
page_sizeintegerNoRows per page, 1 to 50. Default 25. Values outside that range are clamped, not rejected.

Returns: orders, plus page and page_size as applied, total (all matching orders, not just this page) and status (the filter applied, as a status name or active). Each row has order_id (use it with the other tools in this section), order_number, name (the ship-to name stored on the order; may be empty), city, state, zip, country (US when blank), total, status and tracking_number.

Good to know: The filter takes a status name, but each row's status is the display label, so ready-to-ship rows read "Ready to ship", not ChooseShipping. active still includes finished orders, such as delivered and refunded ones. There is no date, customer or text-search filter. An unrecognized status returns an error that lists the accepted names, so send exactly one name, spelled as shown below. Rows are sorted by order date only and can shift if new orders import while you page. The row name is exactly what's on the order (a placeholder such as "Guest" shows as-is), so it can differ from ship_to.name in get_order.

Order statuses: filter by the name; results show the label.

Status nameShown in results as
MissingEmail_InvoiceDraftMissing Email
InvoiceDraftInvoice draft
WaitingForPaymentWaiting for payment
BackOrderedLiveSellingRequested Order
BackOrderedBack Ordered
ChooseShippingReady to ship
LabelRefundedLabel Refunded
PrintLabelLabel purchased
LabelPrintedLabel printed
WaitingForPickupWaiting for pickup
InTransitIn transit
DeliveryFailureDelivery failure
ReturnedToSenderReturned to sender
DeliveryFailureWaitWaiting for return
DeliveredDelivered
PrintReturnLabelPurchase return label
ReturnLabelRefundedReturn label refunded
WaitingForReturnWaiting for return
ReturnInTransitReturn in transit
ReturnDeliveredReturn delivered
ReturnReceivedReturn received
ReturnCompleteReturn complete
RefundedRefunded
WaitingForMergeMerge in progress
MergedMerged
DeletedCanceled

Labels aren't unique: DeliveryFailureWait and WaitingForReturn both show as "Waiting for return", so filter by name. A canceled order's status name is Deleted.

{"status": "active", "page": 1, "page_size": 2}

get_order

Read-only

Returns full detail for one order in your account: the ship-to address, totals, shipping and tracking fields, and line items. It works for an order in any status, including canceled and merged orders.

ParameterTypeRequiredDescription
order_idintegerYesThe order's ID, from list_orders.

Returns: order_id, order_number, status (display label) and date (the order date); ship_to with name, company, street1, street2, city, state, zip, country (US when blank), phone and email; totals with subtotal, shipping (charged to your buyer), tax, total and weight_oz; shipping with method (the purchased label's carrier service), cost (what you paid for the label), ship_date and label_url, where method and label_url stay null until a label is bought (before then, cost can be null or hold an imported or estimated amount, so check label_url rather than cost); tracking_number and tracking_url (a Ship.com tracking page for labels bought through Ship.com); and items, one per line item, each with sku, upc, quantity and price.

Good to know: An ID that doesn't exist and an order in another account return the same error, Order {order_id} was not found in this account. Leaving out order_id returns order_id is required. Line items have no separate product name, style or size field. sku is the item code stored on the line, which can be null or, for some sales platforms, the product name. upc is often null and isn't always a real barcode: orders imported from some sales platforms carry a placeholder value there. price is the price recorded on the line; depending on where the order came from, it can be a unit price or a line total, so don't assume either. ship_to uses the order's saved shipping address when it has one. Otherwise it's built from the order, filling a missing or placeholder name (such as "Guest") and a missing email or phone from the customer's record, and uses "Current Resident" when no name can be found. company is often null. If a label is canceled, the order returns to "Ready to ship" and the label and tracking fields go back to null, though ship_date may keep the old date.

{"order_id": 4815162}

get_label

Read-only

Returns the label files and tracking details for an order's purchased label. It returns stored links only, and never regenerates, re-buys or fetches anything from the carrier. Calling it for an order with no label isn't an error: you get has_label: false and null links.

ParameterTypeRequiredDescription
order_idintegerYesThe order whose label you want.

Returns: order_id; label_url, the printable label formatted for your account's print setting (if your account prints by QR code, USPS labels come back as a scannable QR image instead of a label PDF); raw_label_url, the carrier's original 4x6 label file, present only for some carrier PDF labels (currently certain USPS labels) and null otherwise; form_url, a separate customs form PDF when the carrier provides one (currently UPS international shipments); receipt_url, a carrier receipt when the carrier provides one (currently UPS); tracking_number; tracking_url; and has_label, true when label_url is present.

Good to know: Use has_label to decide whether an order has a live label. Canceling a label clears label_url, tracking_number and tracking_url, but the other links can keep pointing at the voided label's files, so ignore raw_label_url, form_url and receipt_url when has_label is false. Label links are HTTPS file URLs; treat them as opaque rather than relying on their host or format. This tool works on every connection type and returns the label however it was bought, including in the Ship.com web app. Unknown IDs and other accounts' orders return the same not-found error as get_order.

{"order_id": 4815101}

track_order

Read-only

Returns Ship.com's latest stored tracking status for an order. Ship.com keeps it updated automatically in the background; the tool doesn't query the carrier live, so calling it again won't refresh anything. An order without a label returns successfully with null tracking fields.

ParameterTypeRequiredDescription
order_idintegerYesThe order to check tracking for.

Returns: order_id; status, the order's display status, such as "In transit", "Delivered", "Delivery failure" or "Returned to sender" (an order status, not a raw carrier event); tracking_number; tracking_url; ship_date, in_transit_date, delivered_date and exception_date; exception, the carrier's exception text; and last_tracking_update, when Ship.com last stored a tracking update for the order, in UTC.

Good to know: Check last_tracking_update to see how fresh the data is. Not every background update sets it, so it can be null even after status has moved; treat null as "freshness unknown", not "never updated". Other timestamps carry no time zone. If a label is canceled, the status returns to "Ready to ship" and tracking_number, tracking_url and in_transit_date are cleared, but the other dates and exception can keep old values, so read status and tracking_number first. Unknown IDs and other accounts' orders return the same not-found error as get_order.

{"order_id": 4815101}

list_products

Read-only

Lists the active products in your saved Ship.com product catalog, sorted by name from A to Z. Use it to look up product names, descriptions and prices when you build or edit an order. It reads your saved catalog only and doesn't pull products live from connected stores.

ParameterTypeRequiredDescription
querystringNoText to match against the product name or description, case-insensitive. It's a plain substring match: the whole text must appear as typed, with no wildcards. Leading and trailing spaces are ignored. Omit to list every active product.
pageintegerNo1-based page number. Default 1. Values below 1 are treated as 1. A page past the end returns an empty list.
page_sizeintegerNoRows per page, 1 to 50. Default 25. Values outside that range are clamped, not rejected.

Returns: products, plus page and page_size as applied and total (all matching active products, not just this page). Each product has product_id, name, catalog (an optional grouping label), description and price.

Good to know: Inactive products never appear. Products are identified by name: results have no SKU, image, stock, cost or weight fields. Line items in get_order do carry a sku.

{"query": "candle", "page": 1, "page_size": 10}

Creating and changing orders

These tools create orders and change them before they ship. A typical flow: create an order (or up to 100 at once), adjust its details or ship-to address while it is still Ready to ship, then rate it and buy the label. If plans change, void just the label with cancel_shipping_label, or cancel the whole order with cancel_order.

Every order and customer lookup is limited to your own account. An ID from another account gets the same "was not found in this account" answer as one that doesn't exist. Ship.com keeps a record of changes made through MCP, just as it does for changes made in the app. Send numbers and booleans as real JSON numbers and booleans, and send ZIP codes as strings: a ZIP sent as a number loses its leading zero.

create_order

Changes data

Creates one new order at Ready to ship. Ship to a customer already in your address book with customer_id, or pass the recipient inline with ship_to to import an order from anywhere, such as a spreadsheet, an email or a marketplace. Add line items and a package weight now or later. Pass external_order_id so a retry or re-import returns the existing order instead of creating a duplicate.

ParameterTypeRequiredDescription
customer_idintegerOne of twoAn address-book customer (from list_customers or get_customer). The order copies the customer's saved name, company, address, email and phone. Provide exactly one of customer_id or ship_to.
ship_toobjectOne of twoInline recipient. If your address book already has a contact with the same name and street line 1 (ignoring case) and the same ZIP, that contact is reused unchanged and its saved email and phone are copied to the order; otherwise a new contact is created.
ship_to.namestringWith ship_toRecipient's full name, split at the first space into first and last name.
ship_to.companystringNoCompany name.
ship_to.street1stringWith ship_toStreet address line 1.
ship_to.street2stringNoApartment, suite or unit.
ship_to.citystringWith ship_toCity.
ship_to.statestringWith ship_toState or province code.
ship_to.zipstringWith ship_toZIP or postal code. Send it as a string.
ship_to.countrystringNoTwo-letter country code. Default US.
itemsarrayNoLine items. Omit, or send [], for an order with no line items.
items[].namestringYesProduct name. Also adds the product to your General product catalog, or updates the product with the same name there.
items[].quantityintegerNoAt least 1. Default 1.
items[].pricenumberNoUnit price in dollars, 0 or more. Default 0.
items[].skustringNoSKU or UPC, free text.
items[].notesstringNoNotes for this item.
weight_oznumberNoPackage weight in ounces, greater than 0. Saved on the order. trigger_batch_purchase rates at it. get_rates doesn't: pass weight_oz to get_rates explicitly (you can read it back from get_order's totals.weight_oz).
external_order_idstringNoYour source system's order or PO number. If a live order in your account already carries it, that order is returned instead of a new one being created.

Returns: created: true, order_id, a generated 6-character order_number, status ("Ready to ship"), the ship_to used and items_count. With items it adds subtotal (sum of price × quantity) and an equal total; with ship_to it adds customer_id and customer_created. weight_oz and external_order_id are echoed when you send them, and every result ends with a note. A duplicate re-send returns created: false, skipped_existing: true, the existing order's order_id, order_number and current status, and a note.

Good to know: Runs immediately. All input is checked before anything is saved, so a validation error creates nothing. No postage is charged and no carrier is contacted; the address is validated when you rate the order. Duplicate protection ignores canceled and merged orders, so a canceled import can be re-imported. A re-send isn't compared with the original and doesn't update it. The ID is kept in the order's notes as [ext:YOUR-ID], so replacing the notes with update_order turns duplicate protection off for that order. Send retries one at a time: two simultaneous calls with the same ID can both create an order. A catalog product matched by name (ignoring case) takes the new price and its cost resets to 0. If you run a rewards program, the order earns customer points as usual.

{"ship_to": {"name": "Maria Delgado", "street1": "1420 Maple Ave", "street2": "Apt 3B", "city": "Springfield", "state": "IL", "zip": "62704"}, "items": [{"name": "Lavender Soy Candle", "quantity": 2, "price": 18.50, "sku": "CND-LAV-08"}, {"name": "Gift Wrap", "price": 3.00}], "weight_oz": 22, "external_order_id": "SHOP-10482"}

bulk_create_orders

Changes data

Creates up to 100 orders in one call. Each entry takes the same fields as create_order and goes through the same checks, including duplicate protection with external_order_id. One bad entry doesn't stop the rest: every entry gets its own result row, in input order, plus a summary. Use it instead of calling create_order in a loop.

ParameterTypeRequiredDescription
ordersarrayYes1 to 100 order entries. A missing, empty or oversized list is rejected and nothing is created. Split larger imports across calls.
orders[]objectYesOne order with the create_order fields: exactly one of customer_id or ship_to, plus optional items, weight_oz and external_order_id.

Returns: results, one row per entry with index and ok. A successful row has order_id and order_number, plus skipped_existing: true when an existing order was returned; a failed row has error, the same message create_order would give. Also summary (created, skipped, failed) and a note. Rows don't include status, address or totals; use get_order for those.

Good to know: The whole call counts as one request against the rate limit of 60 requests per minute. There is no all-or-nothing: each entry is saved as it is processed. An external_order_id repeated inside the same batch is created once and reported as skipped on the later entry. Send numbers as JSON numbers: a value of the wrong type (such as "two" for a quantity) stops the whole call with an unexpected-error result after earlier entries were already saved. After any unexpected error, check with list_orders. Re-sending the same batch with an external_order_id on every entry is safe, because entries already created come back as skipped.

{"orders": [{"customer_id": 918274, "weight_oz": 8, "external_order_id": "SHOP-10483"}, {"ship_to": {"name": "Maria Delgado", "street1": "1420 Maple Ave", "street2": "Apt 3B", "city": "Springfield", "state": "IL", "zip": "62704"}, "items": [{"name": "Lavender Soy Candle", "quantity": 2, "price": 18.50}], "external_order_id": "SHOP-10482"}]}

update_order

Changes data

Edits an order that is still at Ready to ship or an earlier status: notes, display order number, the amounts you charge your customer, line items and package weight. Send only the fields you want to change, at least one. The total is recalculated every time as subtotal - discount + tax + shipping + handling, using the saved value for any amount you don't send.

ParameterTypeRequiredDescription
order_idintegerYesThe order to edit. Orders at Label Refunded or any later status (label purchased, printed, in transit, delivered and so on) are rejected. Canceled and merged orders aren't rejected; editing them doesn't reopen them.
notesstringNoReplaces the whole notes field. An empty string clears it.
order_numberstringNoDisplay order number. An empty string clears it. Not checked for uniqueness.
subtotalnumberNoItem subtotal. Ignored when items is sent in the same call.
shippingnumberNoShipping you charge your customer (not postage).
taxnumberNoSales tax.
discountnumberNoOrder-level discount, subtracted from the total.
handlingnumberNoHandling fee.
itemsarrayNoReplaces all line items (not a merge). Each item takes name (required), quantity, price, sku and notes, as in create_order. [] removes every item. The subtotal is recalculated from the items.
weight_oznumberNoPackage weight in ounces, greater than 0. Saved on the order. trigger_batch_purchase rates at it. get_rates doesn't: pass weight_oz to get_rates explicitly (you can read it back from get_order's totals.weight_oz).

Returns: updated: true, order_id, order_number, totals (subtotal, shipping, tax, discount, handling, total), plus items_count and weight_oz when you sent them, and a note.

Good to know: Runs immediately and doesn't change the order's status. Rates you got before the change still carry the old weight; call get_rates again before buying. Amounts are saved as sent and aren't range-checked. Items update your General product catalog the same way create_order does. Replacing the notes on an order created with external_order_id removes its [ext:...] reference and with it duplicate protection; include that reference in the new notes to keep it. If you run a rewards program, the customer's points are recalculated. Invoices can't be created or sent through MCP; use the Ship.com app for those.

{"order_id": 4821937, "items": [{"name": "Lavender Soy Candle", "quantity": 3, "price": 18.50, "sku": "CND-LAV-08"}], "shipping": 6.95, "tax": 3.30, "discount": 5.00, "weight_oz": 30}

update_order_address

Changes data

Changes the ship-to street address on an order that is still at Ready to ship or an earlier status. The new address is validated before it is saved; if it doesn't validate, nothing changes. Only this order is updated: the customer's address-book entry and the recipient's name, company, phone and email stay as they are.

ParameterTypeRequiredDescription
order_idintegerYesThe order to re-address. Orders at Label Refunded or any later status are rejected. Canceled and merged orders aren't rejected; editing them doesn't reopen them.
street1stringYesStreet address line 1. Saved as sent.
street2stringNoApartment, suite or unit. If omitted, any existing line 2 on the order is cleared, so re-send it to keep it.
citystringYesCity. The validated city is saved.
statestringYesTwo-letter state code. The validated state is saved.
zipstringYesZIP code, sent as a string. The validated ZIP is saved and can differ from the one you sent.

Returns: updated: true, order_id, the saved ship_to (street1, street2, city, state, zip, with the corrected city, state and ZIP) and a note.

Good to know: Runs immediately. Rates you got before the change still carry the old address; call get_rates again before buying. US addresses only: the address is always validated as a US address. If the validation service is briefly unavailable you get the same "could not validate" error, so a later retry may succeed.

{"order_id": 4821937, "street1": "1420 Maple Ave", "street2": "Apt 3B", "city": "Springfield", "state": "IL", "zip": "62704"}

cancel_order

Changes dataPreview, then confirm

Cancels an order entirely. Its status becomes Canceled and it stays visible in your account. If the order hasn't shipped yet and its label is within 29 days of its ship date, the label is voided first, with the same refund handling as cancel_shipping_label. Customer reward points earned on the order are reversed. To void only the label and keep the order open, use cancel_shipping_label instead.

ParameterTypeRequiredDescription
order_idintegerYesThe order to cancel. Orders in any status are accepted.
confirmbooleanNoOmit or false to preview; nothing changes. true cancels the order.

Returns: A preview returns preview: true, order_id, current_status (for example "Label purchased"), would_void_label, would_reverse_reward_points, refund_note and note. A confirmed call returns canceled: true, order_id, status ("Canceled"), label_voided, postage_balance (your current balance, not a credit from this call) and refund_note (null when there was no label).

Good to know: Call it once without confirm to see what will happen, then again with confirm: true. A label isn't voided once the order is in transit, delivered or being returned, or when the label is more than 29 days past its ship date; the label and tracking then stay on the canceled order and no postage refund is issued. For an order that already shipped, the refund note may describe this as no label to void or as outside the void window. If the carrier refuses the void, the order is still canceled, label_voided is false and no refund is queued; contact help@ship.com if you expected one. would_reverse_reward_points is always true in a preview, even when the order earned no points. The cancellation isn't sent to connected sales platforms; only a voided label sends a tracking update. Cancel each order once. A canceled order can be imported again with create_order using the same external_order_id.

{"order_id": 4821937, "confirm": true}

cancel_shipping_label

Changes dataPreview, then confirm

Voids a purchased shipping label and returns the order to Ready to ship, so you can edit or re-rate it. A label can be voided within 29 days of its ship date. The postage refund isn't immediate: it typically posts to your Ship.com postage balance about 14 days after the void, and is subject to the carrier accepting the void.

ParameterTypeRequiredDescription
order_idintegerYesThe order whose label to void. It must have a purchased label.
confirmbooleanNoOmit or false to preview; nothing is voided. true voids the label.

Returns: A preview returns preview: true, order_id, tracking_number, ship_date, within_void_window, shipping_cost, refund_note and note. A confirmed call returns canceled: true, order_id, status ("Ready to ship") and refund_note.

Good to know: A label past the 29-day window returns an error instead of a preview. When the void goes through, the tracking number, label, rate and insurance are cleared and insurance bought with the label is voided. Unsent follow-up emails to your customer that were triggered by the printed label are canceled. The order leaves any scheduled pickup, but the pickup itself stays booked. If the order came from a connected sales platform that syncs tracking, the change is sent there. When the refund comes due, the label cost and any insurance fee are credited to your postage balance, even if you paid by card, and you get a confirmation email. If the carrier has scanned the package by then, no credit is issued and the tracking number is restored. If the carrier refuses the void, most often because the package is already in transit, nothing changes and no refund is queued. The carrier gives no reason, and a retry the same day may be refused again. Contact help@ship.com if the label should have been refundable. Return labels aren't handled by this tool.

{"order_id": 4821937, "confirm": true}

Rates, labels, and addresses

These tools price shipments, buy labels and check addresses. A typical flow: estimate a cost with quote_shipping (no order needed), rate a specific order with get_rates, then preview and confirm the purchase with buy_label. validate_address checks a US address on its own, and register_ups connects UPS so you can buy UPS labels. buy_label is available on access-token connections; the other four work on every connection and every plan.

Weights are always in ounces and dimensions in inches. Money is in US dollars as plain JSON numbers. rate_id and package_id are long opaque tokens (often 1 to 2 KB): pass them back exactly as received, and take both from the same get_rates response. They capture the price, service, package and ship-to address at the moment you rate. If you change the order's address, weight or dimensions, call get_rates again and use the new values. Rates from quote_shipping can't be bought. Some rating problems are returned as errors even when carriers priced options: a missing or invalid ship-from address ("Rating failed: the account's ship-from address is missing or invalid. Fix it in Settings before rating or buying labels.") and, for get_rates, a destination that failed address validation or needs a customs declaration. Any other problem that still leaves priced options comes back as a warning next to the rates. A warning always starts with "Rating failed: ", even when it's only an informational carrier notice such as a UPS surcharge, so read it before you buy.

quote_shipping

Read-only

Gets shipping rates for a destination without an order, for example to estimate what a package will cost. Every call gets live carrier quotes and skips the short-lived rate cache; Flat Rate prices follow a USPS price list that is refreshed daily. Nothing is saved and no money moves. To buy a label you need an order: use get_rates and buy_label.

ParameterTypeRequiredDescription
zipstringYesDestination postal code. For US destinations send a 5-digit ZIP or ZIP+4 as a string, keeping leading zeros. Any other format is rated as an international destination, even when country is US.
countrystringNoTwo-letter destination country code. Default US. Any other country gets international USPS services and UPS Worldwide services.
weight_oznumberYesPackage weight in ounces. Must be greater than 0.
lengthnumberNoLength in inches, used for dimensional and cubic pricing. Default: unknown.
widthnumberNoWidth in inches. Default: unknown.
heightnumberNoHeight in inches. Default: unknown.
from_zipstringNoOrigin ZIP code. Default: your account's ship-from address. When set, the quote uses a US origin with just this ZIP.

Returns: destination (zip and country, after the US default), weight_oz, rates and, when present, warning. Each rate row has method (display name, such as "USPS Ground Advantage"), rate_id, service_level (usps_ground_advantage, usps_priority, usps_priority_express, usps_intl_express, usps_intl_priority, usps_intl_first_class_package, or a UPS service code such as "03" Ground, "01" Next Day Air, "02" 2nd Day Air, "93" Ground Saver or "11" Standard), rate (what your account pays, including any per-label charge your plan adds), retail_rate (retail price for comparison, or null), delivery_date (the carrier's estimate as an ISO date-time without a time zone; always null on UPS rows), is_cubic, and variant and max_weight_oz (set on Ground Advantage weight-band rows, such as "up to 12 oz" and 12; otherwise null).

Good to know: Rows come in a fixed order: Priority Mail Flat Rate products, then Priority Mail, Priority Mail Express (shown as "USPS Express Mail") and Ground Advantage, then the Ground Advantage weight bands, then international USPS services, then UPS. Options that didn't price are left out, and similar rows aren't merged: Ground Advantage can appear twice, once as a cubic row ("(Cubic)" or "(Cubic Soft Pack)" in method) and once plain. Weight-band rows whose limit is below your weight are dropped, and so are all band rows when a side is over 22 inches or the volume is over 1,728 cubic inches. UPS rows can appear before your account is registered with UPS, but buying a UPS label requires register_ups. A weight without dimensions can quote lower than the carrier later bills, because dimensional charges come back as postage adjustments, so give real dimensions when you have them. One package per call, shipping today, with no signature, Saturday delivery or package-type options. Errors: "zip is required.", "weight_oz must be greater than 0.", the ship-from error, or "Rating failed: " with the carrier's message when nothing priced.

{"zip": "30309", "weight_oz": 12, "length": 10, "width": 8, "height": 4}

get_rates

Changes data

Gets shipping rates for one of your orders, using the order's ship-to address and your ship-from address. It returns a package_id and a list of priced options. To buy one, pass that option's rate_id, method and service_level, plus the package_id, into buy_label exactly as returned. You can override the package weight and dimensions.

ParameterTypeRequiredDescription
order_idintegerYesThe order to rate. It must belong to your account.
weight_oznumberNoPackage weight in ounces. The weight saved on the order is not used for pricing, so pass it here (get_order returns it as totals.weight_oz). Omit it or send 0 for a weightless quote (fixed-price options only). A positive value gets a full quote and is saved as the order's package weight. Negative values aren't rejected, so send only positive weights. Default 0.
lengthnumberNoLength in inches. Omit it, or send 0 or less, to use the length saved on the order, if any.
widthnumberNoWidth in inches. Same fallback as length.
heightnumberNoHeight in inches. Same fallback as length.
ship_datestringNoShip date as YYYY-MM-DD. Default today. A value that can't be read quietly becomes the current time. buy_label uses its own ship_date when you purchase.

Returns: order_id, package_id (an opaque token for buy_label), ship_date (ISO date-time in UTC), rates (the same row fields as quote_shipping), a note reminding you to pass the chosen values into buy_label verbatim, and warning when present.

Good to know: Without a weight you get only the fixed-price options: the six Priority Mail Flat Rate products and the four Ground Advantage weight bands, minus any band below the order's saved weight (an order saved above 15.9 oz may get only Flat Rate rows). To see Priority Mail, Priority Mail Express, Ground Advantage at the real weight, UPS and international services, pass a positive weight_oz. Flat Rate box and weight-band prices may be served from a rate cache for up to 3 hours, and Flat Rate prices follow a USPS price list that is refreshed daily.

It never buys anything or moves money, but like rating in the Ship.com web app it saves to the order: a positive weight_oz becomes the order's package weight, and for US destinations the ship-to address is checked with carrier address validation and the result is stored on the order. A valid address can have its city and state corrected. Nothing is saved when the validation service is unavailable. It's annotated read-only in tools/list because it never purchases.

Choose a Ground Advantage weight-band row only if the package weighs no more than its max_weight_oz. The label is bought at the band's weight and a standard box size, so a heavier or larger package is billed the difference later as a postage adjustment. Bands that can't fit are dropped when the weight or dimensions are known; with neither known, all four come back. UPS rows can appear before your account is registered with UPS (buying one needs register_ups), and a PO Box destination gets only UPS Ground Saver from UPS.

An order that doesn't exist or isn't yours returns "Order <id> was not found in this account." A destination that fails address validation returns "Rating failed: the destination address failed validation. Correct it with update_order_address before buying a label." (see update_order_address). Shipments that need a customs declaration can't be rated or bought through MCP yet: destinations outside the US; APO, FPO and DPO addresses; US territories and freely associated states (Guam, the U.S. Virgin Islands, American Samoa, the Northern Mariana Islands, Micronesia, the Marshall Islands and Palau); and any order when your own ship-from address is APO, FPO or DPO. Ship those from the Ship.com web app. One package per order per call, with no signature, Saturday delivery, return label or custom package options.

{"order_id": 48213, "weight_oz": 14, "length": 10, "width": 8, "height": 4}

Response (tokens shortened). package_id is shared by every row; rate_id, method and service_level come from the row you choose. Weight-band rows set variant and max_weight_oz:

{
  "order_id": 48213,
  "package_id": "eyJhbGciOi...",
  "ship_date": "2026-10-05T14:02:11Z",
  "rates": [
    {"method": "USPS Ground Advantage", "rate_id": "eyJhbGciOi...", "service_level": "usps_ground_advantage", "rate": 7.42, "retail_rate": 9.35, "delivery_date": "2026-10-08T00:00:00", "is_cubic": false, "variant": null, "max_weight_oz": null},
    {"method": "USPS Priority", "rate_id": "eyJhbGciOi...", "service_level": "usps_priority", "rate": 10.15, ...}
  ],
  "note": "Pass a chosen rate's rate_id + this package_id + its method + service_level verbatim into buy_label."
}

buy_label

Changes dataSpends moneyPreview, then confirmAccess-token connections only

Buys a real USPS or UPS shipping label for an order, paid from your postage balance. Pass the values from get_rates exactly as returned. Without confirm it returns a preview of the price, your balance and whether an auto-recharge would run, and buys nothing. Call again with confirm: true to purchase.

ParameterTypeRequiredDescription
order_idintegerYesThe order to buy a label for. It must belong to your account.
rate_idstringYesThe chosen option's rate_id. It decides what is bought: carrier, service, price and package.
package_idstringYesThe package_id from the same get_rates response.
methodstringYesThe option's method, saved on the order as the shipping method name. It doesn't change what is bought.
service_levelstringYesThe option's service_level. It doesn't change what is bought.
ship_datestringNoYYYY-MM-DD. Default today; unreadable values become today. A date more than 24 hours in the past is refused. For USPS, the mailing date is kept between today and 7 days ahead (US Eastern time).
confirmbooleanNotrue to buy. Omit or false for a preview. Default false.
max_costnumberNoCeiling in dollars for the postage price (insurance and fees not counted). A higher rate is refused, on preview and confirm, before anything is charged. An equal rate passes.
insurancebooleanNoOverrides your account's insure-all-shipments setting for this label. Default: that setting.
insured_valuenumberNoDollar value to insure when insurance is on. Default: the order's merchandise subtotal, else its total. With no positive value to insure, the label ships uninsured.

Returns: A preview returns preview: true, order_id, method, service_level, price (postage), pay_as_you_go_fee, insurance, insured_value, insurance_cost, total_charge, current_balance, balance_after, balance_would_go_negative, auto_recharge_amount, auto_recharge_would_fire and a message. A purchase returns purchased: true, order_id, cost (postage), insurance, insured_value, insurance_cost, label_url (Full Size: a 4x6 label on a US Letter page), tracking_number, tracking_url (https://app.ship.com/tracker/ plus the tracking number), form_url (a customs form when one applies, otherwise null), balance_after, a message, status (the order's status, normally "Label purchased") and next_step. While the label is unprinted, next_step says to mark it printed once it's printed, then book a pickup if your plan includes carrier pickup or drop it off; for a UPS label it points to the paid UPS pickup in the Ship.com web app. Otherwise it's null.

Good to know: Available on access-token connections. It is not offered on connections authorized through Ship.com sign-in (OAuth), such as the Claude directory connector and claude.ai custom connectors; there it is absent from tools/list, and a call returns JSON-RPC error -32602 saying the tool is not available. Confirm doesn't need an earlier preview, and the preview doesn't check package_id, plan limits, billing, ship date, address or account standing, so a clean preview can still fail on confirm.

Postage comes out of your postage balance, plus a $0.30 pay-as-you-go fee when your auto-recharge amount is $0.01 or less. Insurance is a separate charge: $0.99 for each $100, or part of $100, of insured value on orders to the US ($40 costs $0.99, $120 costs $1.98), and $1.25 per $100 otherwise. If your balance can't cover postage and insurance, your payment method on file is charged before the label is created, for the larger of your auto-recharge amount and the shortfall. With an auto-recharge amount of $0.01 (pay as you go), the charge is the shortfall plus $0.30, at least $1, and the preview's auto_recharge_would_fire still reads false. With an amount of $0, the purchase fails instead. A recharge stays on your balance even if the purchase then fails, for example at your plan's monthly shipment limit.

On success the order moves to Label purchased with its tracking number and label saved. It moves to Label printed when it's printed from the Orders page or a batch's purchase page in the Ship.com web app. Labels printed straight from label_url, or with Ship Mode's print-all, need a call to mark_labels_printed once they're printed. Carrier pickups collect printed labels only (see Pickups). An order still awaiting payment is marked as paid outside Ship.com. The tracking number is sent to the order's sales platform, such as Shopify, Etsy, eBay, WooCommerce, Square, Squarespace, Wix or PayPal. Customer emails and tracking webhooks go out later, when the label is marked printed. Your saved label format is set to Full Size. If the order already has a label, that label is returned and nothing is charged; the response still shows purchased: true, with cost from the rate you passed. Simultaneous purchases for one order return the first label.

Errors: A token that can't be read returns "rate_id is invalid or has expired. Call get_rates again and use a fresh rate_id." A rate over max_cost returns "Refusing to buy: rate is $<price> which exceeds max_cost $<max>. Nothing was purchased." Purchase failures read Label purchase failed — <Code>, sometimes followed by : <detail>. The common codes:

CodeMeaningWhat to do
NeedsFromAddressYour account has no ship-from address.Add one in Settings in the Ship.com web app.
MissingBillingYour balance is too low and no payment method is on file.Add a payment method in the web app.
AutoRechargeOffYour balance is too low and your auto-recharge amount is $0.Add funds or set an auto-recharge amount in the web app.
NeedPayment, ChargeFailedA card charge was declined, or a subscription payment is due.Update your payment method in the web app.
CooldownNeededRepeated card declines.Wait 60 seconds, then fix the card before retrying.
Limit reachedYour plan's monthly shipment limit; the detail is the limit.Upgrade, or wait for next month.
UnauthorizedThe package_id is from a different get_rates response than the rate_id.Rate again and use both values from one response.
UPSErrorUPS couldn't create the label, including when your account isn't registered with UPS.Read the detail; run register_ups if you haven't registered.
SuspendedYour account is on hold for review.Contact help@ship.com.

The preview checks none of these. If balance_would_go_negative is true and auto_recharge_amount is 0, the confirm will fail with AutoRechargeOff (or MissingBilling when no payment method is on file). Other codes, such as DateError, RateError or ShippingError, come with a detail that describes the problem, often the carrier's own message. A label that can't be stored is voided automatically. If a failure says it's not certain whether a label was bought, or you get an unexpected error, check get_label and get_balance before retrying.

{"order_id": 48213, "rate_id": "<rate_id from get_rates>", "package_id": "<package_id from get_rates>", "method": "USPS Ground Advantage", "service_level": "usps_ground_advantage", "max_cost": 8.00}
{"order_id": 48213, "rate_id": "<same rate_id>", "package_id": "<same package_id>", "method": "USPS Ground Advantage", "service_level": "usps_ground_advantage", "max_cost": 8.00, "confirm": true}

validate_address

Read-only

Checks a US address with carrier address validation, the same check Ship.com runs on orders, and returns a standardized version. It saves nothing, so use it to check an address before you create or update an order. US addresses only.

ParameterTypeRequiredDescription
streetstringYesStreet address line 1, such as "1600 Pennsylvania Ave NW". Returned unchanged.
street2stringNoSecond line: apartment, suite or unit. Returned unchanged, or null if omitted.
citystringYesCity.
statestringYesTwo-letter state code, such as DC.
zipstringYes5- or 9-digit US ZIP code. Only the first 5 digits are used for matching.

Returns: is_valid (boolean), standardized (street, street2, city, state, zip) and, only when is_valid is false, a note. When the address is valid, city, state and zip are the validator's standard values: the city is usually uppercase, and the ZIP can come back as ZIP+4 and can differ from the one you sent. When it isn't valid, they echo your input.

Good to know: An address is valid only when there's a single, unambiguous match. Street lines are never corrected. is_valid: false means either that no match exists or that the validation service was briefly unavailable; the result doesn't say which, so retry an address you know is good later. Missing fields are reported together, for example "street is required." or "city, zip are required." To correct an order's saved address, use update_order_address.

{"street": "742 Evergreen Ter", "street2": "Apt 2", "city": "Springfield", "state": "IL", "zip": "62704"}

register_ups

Changes dataPreview, then confirm

Registers your Ship.com account with UPS, which is required before you can buy UPS labels. It can open a new UPS account or link one you already have. Registering accepts the UPS Technology Agreement, a legally binding contract with UPS Market Driver, Inc. Call it without confirm to get the agreement and a token, show the agreement to the account holder, and confirm with the token only after they explicitly accept it. An AI assistant or integration must never accept on their behalf.

ParameterTypeRequiredDescription
confirmbooleanNoOmit or false to preview; nothing is registered. true to register. Default false.
accepted_agreement_tokenstringWith confirmThe agreement_token from the preview. Send it only after the account holder has seen the agreement and accepted it.
companystringNoBusiness name. Default: your account's business name, else the contact name.
namestringNoContact's first and last name (at least two words; the last word is the last name). Default: the account holder's name.
emailstringNoContact email. Default: your account email.
phonestringNoContact phone. Default: your account phone.
street1stringNoPickup address line 1 (only line 1 is used). Default: your account address.
citystringNoCity. Default: your account address.
statestringNoTwo-letter state code. Default: your account address.
zipstringNoZIP code. Default: your account address.
existing_ups_accountstringNoAn existing UPS account number (exactly 6 letters or digits) to link instead of opening a new account. UPS checks it first at no cost: a number UPS rejects is refused, and if UPS can't give an answer, it's linked anyway.
existing_ups_postal_codestringNoThe postal code UPS has on file for that account. Default: zip.

Returns: A preview returns preview: true, registered (whether the account is already registered), note, agreement_version (such as UTA10072022), agreement_token (64 hex characters), prefill (the details that would be sent: company, name, email, phone, street1, city, state, zip, country "US"), missing_fields and, last, agreement_text (the full agreement as plain text, about 71 KB). A confirm returns registered: true, ups_account_last3 (a masked account number such as ***2C3), agreement_version and a note.

Good to know: The token is specific to your account and comes only from a preview. It can change, for example when the agreement is updated, so if a confirm refuses the token, preview again. Contact and address fields are required whether you open or link an account; missing ones are listed in missing_fields, and a confirm without them is refused. Opening a new account sends UPS your company, contact name, email, phone, address and the IP address of the request. There is no charge. Registration can't be undone through MCP, and an account that's already registered is refused (the preview shows registered: true). UPS rows can show up in quote_shipping and get_rates before you register, but buying them fails until you do. US addresses only. UPS-side problems come back as "UPS registration failed: " followed by the reason.

{"confirm": true, "accepted_agreement_token": "<agreement_token from the preview>", "existing_ups_account": "A1B2C3", "existing_ups_postal_code": "30309"}

Pickups

These tools book free USPS carrier pickups, so the carrier collects your label-printed packages instead of you dropping them off. A typical flow: mark the labels you've printed with mark_labels_printed, check a date with check_pickup_availability, preview and then book with schedule_pickup, review with list_pickups and get_pickup, and cancel with cancel_pickup if plans change. Booking a pickup needs a plan that includes carrier pickup; the other five tools work on every plan.

Print and mark first. A pickup collects only orders at Label printed. Labels bought with buy_label or trigger_batch_purchase stay at Label purchased until they're marked printed. In the Ship.com web app, printing from the Orders page or a batch's purchase page marks a label, and so does the shipping window opening it automatically (except for orders with a customs form). Labels printed straight from label_url, or with Ship Mode's print-all, stay at Label purchased; call mark_labels_printed for those before schedule_pickup. Only free pickups are booked or canceled here. UPS-labeled packages are never collected by these pickups: a UPS pickup is a separate paid visit that you schedule and cancel in the Ship.com web app. Paid UPS pickups still show up in list_pickups and get_pickup, with carrier "UPS" and a fee_amount. Pickups use your account's pickup address, or your ship-from address if you haven't set a separate one. Refer to a pickup by its pickup_id, not the carrier's confirmation number. Every pickup lookup is limited to your own account, and an ID from another account gets the same "was not found in this account" answer as one that doesn't exist. Send dates as YYYY-MM-DD only. Carrier names differ by tool: check_pickup_availability, schedule_pickup and cancel_pickup report USPSDirect or MoveMethod (both are USPS pickups; treat the value as informational), while list_pickups and get_pickup report every free pickup as USPS/MoveMethod.

mark_labels_printed

Changes dataSends emailPreview, then confirm

Marks purchased labels as printed, the same as printing them in the Ship.com web app. Orders move from Label purchased to Label printed, which is what a carrier pickup collects. Call it after buy_label or trigger_batch_purchase, once the labels are actually printed. get_label gives the files: label_url, plus form_url for international orders, whose customs form must be printed too.

ParameterTypeRequiredDescription
order_idsarray of integersThis or batch_idThe orders whose labels you printed, up to 100. Duplicates are ignored; every entry must be a whole number.
batch_idintegerThis or order_idsA batch_id from trigger_batch_purchase or get_batch_status. Marks that batch's bought but unprinted labels, up to 100 per call; call again to continue.
confirmbooleanNoOmit or false to preview; nothing changes. true marks the labels printed.

Returns: A preview returns preview: true, would_mark_count, would_mark_order_ids, already_printed_order_ids, skipped (each with order_id, status and a reason), free_pickup_order_ids (labels a free pickup will collect once printed), ups_order_ids, outside_pickup_window_order_ids, customs_form_order_ids (labels with a customs form to print too), not_found_order_ids (with order_ids), batch_id and remaining_unprinted_in_batch (with batch_id) and a note. A confirmed call returns marked_count, marked_order_ids, already_printed_order_ids, skipped, failed (each with order_id and a reason), not_found_order_ids, batches_marked_printed, free_pickup_eligible_count, status ("Label printed" when at least one order was marked or was already printed, otherwise null), batch_id and remaining_unprinted_in_batch (with batch_id) and next_step: what to do next, or null. If unprinted labels remain in the batch, it says to finish marking them first: a pickup collects only labels already marked printed, and you can book one free pickup per date. Otherwise, when a free pickup would collect the labels, it suggests schedule_pickup if your plan includes carrier pickup, or dropping them off with the carrier.

Marking a label printed has the same effects as printing it in the web app. Each customer is sent your account's Label Printed tracking email about an hour later, if you have that email turned on. Partner tracking webhooks fire, and so do any automations set to run when a label is printed. That's why it previews first: confirm only after the labels are really printed. For the same reason the tool is annotated destructiveHint: true, so your client may ask before running it. No money moves.

Good to know: Only orders at Label purchased with a label on file change. Orders already at Label printed are listed in already_printed_order_ids and left alone, so repeating a call is safe. Everything else is skipped with a reason and never moved backward: canceled or merged orders, orders with no label bought (including voided labels), and orders past printing, such as Waiting for pickup, In transit or Delivered. UPS labels aren't collected by schedule_pickup; book the paid UPS pickup in the Ship.com web app. A label whose ship date is more than 3 days old, or that's already on a pickup, isn't collected by any carrier pickup, so drop it off instead. The confirmed call checks every order again, so it can differ from the preview. One failed order doesn't stop the rest; call again for the IDs in failed. While a batch's labels are still being bought, both calls return marked_count: 0 and a note to check get_batch_status, and nothing changes. Works on every plan and every connection type. IDs that aren't yours are listed in not_found_order_ids; if none are yours you get None of those orders were found in this account., and an unknown batch returns Batch {batch_id} was not found in this account. Sending both order_ids and batch_id, or neither, returns an error.

{"order_ids": [48213, 48214]}
{"batch_id": 91544, "confirm": true}

check_pickup_availability

Read-only

Asks the carrier whether it can pick up from your pickup address on a given date. It books nothing and changes nothing in Ship.com. The carrier is chosen the same way schedule_pickup chooses it, from the label-printed packages a pickup would collect. Unlike the schedule_pickup preview, it also returns the carrier's reason when an address isn't eligible.

ParameterTypeRequiredDescription
datestringYesThe pickup date you want, as YYYY-MM-DD.

Returns: available (boolean), pickup_date (YYYY-MM-DD, or null), carrier (USPSDirect or MoveMethod) and error (the carrier's explanation when pickup isn't available, otherwise null).

Good to know: For USPS, the usual carrier, a successful check means your address is eligible for carrier pickup. It doesn't reserve the date: USPS normally returns no date of its own, so pickup_date echoes the date you asked for, and USPS accepts or refuses the date when you book. If USPS does suggest a next eligible date, pickup_date is that date and available is true only when it falls on or before your requested date. An ineligible address or a carrier problem is a normal result, not an error: available: false, pickup_date: null and the carrier's text in error. Ship.com doesn't check for past dates or weekends; the carrier decides. With no label-printed packages waiting, or only UPS ones, it normally checks USPS Direct. Labels that are bought but not marked printed don't count; mark them with mark_labels_printed first. Works on every plan, including plans without carrier pickup. A missing or unreadable date returns date is required, formatted YYYY-MM-DD.

{"date": "2026-10-06"}

schedule_pickup

Changes dataPreview, then confirmRequires: Carrier pickup

Books a free carrier pickup at your pickup address. One pickup collects every eligible package at once, and you can't choose a subset. Every collected order moves to Waiting for pickup. UPS-labeled packages are never included; book a paid UPS pickup in the Ship.com web app instead.

A package is eligible when its order is at Label printed, isn't already on a pickup, doesn't have a UPS label, and has a ship date no earlier than 72 hours ago (future ship dates count). Packages on a SCAN form qualify the same way. An order whose label was bought but not yet marked printed stays at Label purchased and isn't collected. The preview lists those labels in unprinted_order_ids and says so in its note; once they're printed, mark them with mark_labels_printed and preview again. There's no limit on how many packages one pickup collects.

ParameterTypeRequiredDescription
datestringYesThe pickup date, as YYYY-MM-DD.
locationstringYesWhere the carrier should collect the packages. One of In/At Mailbox, Front Door, Back Door, Side Door, Knock on Door/Ring Bell, Mail Room, Office, Reception or Other. Case doesn't matter, but the text must otherwise match exactly, with no extra spaces. Use Other together with special_instructions that describe the spot. Saved as sent.
special_instructionsstringNoA note for the carrier. For USPSDirect pickups, emoji and other non-ASCII characters are removed and punctuation other than - . , ' & becomes a space before it's sent, because USPS rejects characters such as ! and ?. Your account keeps the note as you wrote it.
confirmbooleanNoOmit or false to preview; nothing is booked. true books the pickup.

Returns: A preview returns preview: true, would_schedule, pickup_date (your requested date), location, carrier, available, eligible_order_count, eligible_order_ids, ups_eligible_count (UPS-labeled packages waiting that this pickup won't collect), already_scheduled_on_date, unprinted_label_count and unprinted_order_ids (bought labels this pickup would collect once they're marked printed) and a note. A confirmed call returns scheduled: true, pickup_id (use it with get_pickup and cancel_pickup), carrier_confirmation (the carrier's confirmation number; rarely null when the carrier returns none, and list_pickups then shows "0"), carrier, pickup_date, order_ids, order_count and status ("Waiting for pickup", the new status of the collected orders).

Good to know: Your plan must include carrier pickup (Growth, Executive or Ultra). The plan is checked first, even for a preview, and otherwise you get Your current Ship.com plan does not include carrier pickup scheduling. Upgrade the subscription to use this tool. Call once without confirm to see what would be collected, then again with confirm: true. The confirmed call re-checks everything at that moment, so its counts can differ from the preview if orders changed in between. would_schedule is true when there are eligible packages and no free pickup on that date yet; it doesn't reflect available, and the preview doesn't include the carrier's reason, so use check_pickup_availability to see why. You can have one free pickup per date; a second is refused until you cancel the first. A paid UPS pickup on the same date doesn't count. One booking runs at a time per account. A second confirmed call waits for the first to finish and then runs its own checks, so a duplicate for the same date is refused. If the first is still running after about 20 seconds, the second returns an error and books nothing. With nothing eligible, the confirmed call returns No orders can be set to pickup. If bought but unprinted labels are waiting, that error also lists their order IDs to mark printed: the first 100, then how many more, since mark_labels_printed takes up to 100 per call. Other errors don't include the list. A carrier refusal, such as an invalid date or characters USPS won't accept, comes back word for word and nothing is booked. To book the visit, the carrier receives your contact details, such as your name, business name, email and phone. Pickups are free: nothing is charged to your postage balance or card, and Ship.com sends no email.

{"date": "2026-10-06", "location": "Front Door", "special_instructions": "Packages on the bench by the garage"}
{"date": "2026-10-06", "location": "Front Door", "special_instructions": "Packages on the bench by the garage", "confirm": true}

cancel_pickup

Changes dataPreview, then confirm

Cancels a free pickup with the carrier. Every order linked to the pickup goes back to Label printed and is detached from it, so a later pickup can collect it. Paid UPS pickups are refused in both the preview and the confirmed call; cancel those in the Ship.com web app, where the fee is refunded to your postage balance.

ParameterTypeRequiredDescription
pickup_idintegerYesThe pickup's pickup_id, from schedule_pickup, list_pickups or get_pickup. Not the carrier's confirmation number.
confirmbooleanNoOmit or false to preview; nothing changes. true cancels the pickup.

Returns: A preview returns preview: true, pickup_id, carrier_confirmation, pickup_date, status (the carrier's status, such as "scheduled" or "canceled"), canceled, linked_order_count (orders currently linked, in any status) and a note. A confirmed call returns canceled: true, pickup_id, carrier (USPSDirect or MoveMethod), reverted_order_ids, reverted_order_count and status ("Label printed", the new status of those orders).

Good to know: Call once without confirm to see the pickup and how many orders would revert, then again with confirm: true. The preview doesn't contact the carrier. Orders change only after the carrier confirms the cancellation; if the carrier refuses, its error comes back and the pickup stays scheduled with nothing changed. Every linked order is set back to Label printed whatever its current status, so cancel only pickups that haven't happened yet. An already-canceled pickup previews normally, with the note "This pickup is already canceled."; confirming it returns Pickup {pickup_id} is already canceled. without contacting the carrier. An ID that doesn't exist or belongs to another account returns Pickup {pickup_id} was not found in this account. Works on every plan. No money moves and Ship.com sends no email.

{"pickup_id": 51874, "confirm": true}

list_pickups

Read-only

Lists your most recent pickups, newest first, including canceled pickups and paid UPS pickups. Use a row's pickup_id with get_pickup to see its orders, or with cancel_pickup to cancel a free one.

ParameterTypeRequiredDescription
limitintegerNoHow many pickups to return, 1 to 50. Default 10. Values outside that range are clamped, not rejected.

Returns: pickups and count (the rows returned, not an account total). Each row has pickup_id, carrier_confirmation, date (YYYY-MM-DD), status (the carrier's status text), canceled, created, location, special_instructions (as you wrote it, or null), carrier (USPS/MoveMethod for free pickups, UPS for paid ones) and fee_amount (the UPS fee charged to your postage balance; null for free pickups).

Good to know: Rows are ordered by when the pickup was created, not by pickup date. There's no paging, so only your 50 most recent pickups can be reached. The date field is date here but pickup_date in the other pickup tools. created is a UTC time without a zone designator, such as 2026-10-05T18:42:11.523. Rows don't include orders. It reads Ship.com's records only and doesn't contact the carrier. Works on every plan.

{"limit": 5}

get_pickup

Read-only

Returns one pickup and the orders linked to it. Use it to see which packages a pickup will collect, or to check a pickup before you cancel it with cancel_pickup.

ParameterTypeRequiredDescription
pickup_idintegerYesThe pickup's pickup_id, from schedule_pickup or list_pickups. Not the carrier's confirmation number.

Returns: pickup_id, carrier_confirmation, pickup_date (YYYY-MM-DD), status (the carrier's status text), location, special_instructions, created, canceled, canceled_date (null unless canceled), carrier (USPS/MoveMethod for free pickups, UPS for paid ones), fee_amount (the UPS fee charged to your postage balance; null for free pickups), orders and order_count. orders lists every order linked to the pickup, newest order date first, each with order_id, status (the display label, such as "Waiting for pickup", "In transit" or "Delivered") and tracking_number.

Good to know: A canceled pickup normally shows an empty orders list, because cancelling detaches its orders. created and canceled_date are UTC times without a zone designator. An ID that doesn't exist or belongs to another account returns Pickup {pickup_id} was not found in this account., and a missing ID returns pickup_id is required. It reads Ship.com's records only and doesn't contact the carrier. Works on every plan.

{"pickup_id": 51874}

Batch shipping and platform sync

These tools handle work in bulk: pulling new orders in from a connected store and buying labels for many orders at once. A typical flow: check your stores with list_connected_platforms, import recent orders with sync_platform_orders and follow the job with get_sync_status, then preview and confirm a batch with trigger_batch_purchase (access-token connections only) and follow it with get_batch_status. Both kinds of work run in the background, so each starts with a call that returns an ID and finishes with a status tool you poll.

Every request counts toward the limit of 60 requests per minute per token, including initialize and tools/list. When polling a status tool, wait a few seconds between calls. Over the limit you get HTTP 429 with Retry-After: 60. Batches and sync jobs are limited to your own account: an ID from another account is treated like one that doesn't exist.

trigger_batch_purchase

Changes dataSpends moneyPreview, then confirmRequires: Batch shippingAccess-token connections only

Buys USPS Ground Advantage labels for many ready-to-ship orders at once. Without confirm it rates every eligible order and returns per-order prices and a postage total, buying nothing. With confirm: true it rates the orders again and queues the batch. A background worker then buys one label per rated order, usually within about a minute; follow it with get_batch_status. Bought labels aren't marked printed: once they're printed, call mark_labels_printed with the batch_id so a carrier pickup can collect them.

ParameterTypeRequiredDescription
order_idsarray of integersNoOrders to include. Omit to include every Ready to ship order. Only orders at Ready to ship with a street address are used; other IDs (wrong status, no address, not found) are skipped with no row and no error. Entries that can't be read as a whole number, including decimals such as 12.0, are ignored. An empty array, an array with no usable IDs, or a value that isn't an array also includes every Ready to ship order.
max_totalnumberNoSpending ceiling in dollars for the batch's postage (insurance premiums not counted). In a preview, going over sets over_max_total: true. On confirm, the fresh total is checked and the batch is refused if it is higher. 0 or less refuses any batch that costs anything. Default: no ceiling.
weight_oznumberNoFallback package weight in ounces, used only for orders with no saved weight. Orders that have a weight always rate at their own. Without it, an order with no weight gets a per-order error.
confirmbooleanNoOmit or false to preview. true to queue the batch for purchase. Default false.
insurancebooleanNoOverrides your account's insure-all-shipments setting for this batch. When on, each order is insured for its order total; orders with a $0 total ship uninsured. Default: your account setting.

Returns: A preview returns preview: true, orders, rated_count, failed_count, total_cost (postage for the rated orders), insurance, over_max_total, in_progress_batch (always null) and a note. Each order row has order_id, name, city, state and either rate, method ("USPS Ground Advantage", plus " (Cubic)" or " (Cubic Soft Pack)" when cubic pricing applies) and service_level (such as usps_ground_advantage), or an error explaining why it couldn't be rated, such as a missing weight or an APO/FPO address. Rows are sorted by recipient name. A confirm returns batch_id, requested_count (orders queued), total_cost (postage from the fresh rating), insurance and a note.

Good to know: Available on access-token connections. It is not offered on connections authorized through Ship.com sign-in (OAuth), such as the Claude directory connector and claude.ai custom connectors; there it is absent from tools/list, and a call returns JSON-RPC error -32602 saying the tool is not available. get_batch_status works on every connection. Nothing links a confirm to an earlier preview: confirm rates every order from scratch, so its total can differ, and orders that fail to rate are left out without being listed. If no order can be rated, confirm returns an error and queues nothing. To pin both the orders and the spend, confirm with the same order_ids and a max_total near the previewed total. One batch can be in progress per account, including batches started in the Ship.com web app; until it finishes, preview and confirm return an error that names its batch ID.

Money moves when the worker buys the labels. Each label's postage comes out of your postage balance, and insurance premiums, when on, are charged on top of the postage total and aren't in the preview. With auto-recharge on, if your balance can't cover the batch's postage, your payment method on file is charged once up front for the shortfall or your auto-recharge amount, whichever is larger; if that charge can't be made because there's no payment method or the account is suspended, no labels are bought. If a label fails for a payment reason, the worker stops and marks the remaining orders Not attempted to avoid repeated charges. Each label counts toward your plan's monthly shipment limit, which the preview doesn't check. An order that already has a label isn't bought twice.

Every call, preview included, requests a live USPS rate for each order, so large batches are slow. There is no batch size limit and no service choice. Labels are parcels with no signature, ship-dated when the batch is queued, in your saved label format. Rating, even in a preview, saves the recipient address and weight it used (including a weight_oz fallback) onto each order. Batch rating doesn't run address validation; if an order fails to rate, try get_rates on it alone to see the carrier's reason.

{"order_ids": [48210551, 48210554, 48210560], "max_total": 25.00, "weight_oz": 8}
{"order_ids": [48210551, 48210554, 48210560], "max_total": 25.00, "weight_oz": 8, "confirm": true}

get_batch_status

Read-onlyRequires: Batch shipping

Reports the progress or result of a batch purchase. Omit batch_id to get the batch in progress, or your most recent batch if none is running. It reads every batch on your account, including batches started in the Ship.com web app, and works on every connection type.

ParameterTypeRequiredDescription
batch_idintegerNoThe batch_id from trigger_batch_purchase. Default: the batch in progress, else the most recent batch.

Returns: While labels are being bought: complete: false, batch_id, requested, completed (orders attempted so far, failures included; 0 until the worker starts) and a note. When finished: complete: true, batch_id, orders, purchase_success, purchase_failed, requested, total (postage plus insurance) and shipping_balance (your postage balance). Each order has order_id, name, city, state, shipping_cost (postage plus insurance), tracking_number and status (such as "Label purchased" or "Label printed"), sorted by order date. unprinted_count says how many are bought but not yet marked printed; when it's above 0, next_step tells you to call mark_labels_printed with this batch_id once they're printed. If labels failed, failed_orders lists each order_id with a reason; older batches instead return one combined errors string and a limit_reached flag. A reason is a short code, optionally followed by " - " and detail, for example "Limit reached - 100" when the monthly shipment limit was hit, or "Not attempted - ..." for orders skipped after a payment problem stopped the batch. With no batches, or an unknown batch_id, you get no_status: true and a note as a normal result, not an error.

Good to know: Needs the same plan feature as trigger_batch_purchase, so after moving to a plan without batch shipping you can't read past batches here. orders and purchase_success cover the batch's orders from Label purchased through Delivered, so printed, picked-up and shipped orders stay listed. Orders whose label was voided or that were canceled drop out, as do orders in a return, so purchase_success plus purchase_failed may not equal requested. A batch that stopped before buying anything, for example because the up-front payment couldn't be made, reports complete: true with no orders and no failure detail. A canceled batch also reports complete: true; its voided orders drop out of orders. Rarely, a batch that failed before processing began keeps reporting complete: false; if completed stays at 0 for several minutes, check the orders with list_orders. No label files here: use get_label per order, and get_balance for your current balance.

{"batch_id": 91544}

list_connected_platforms

Read-only

Lists the sales platforms connected to your account, with each connection's status and last sync time. It reads what Ship.com has stored and doesn't contact the platforms.

No parameters.

Returns: platforms and count. Each platform has name, status, error and last_sync. Only connected platforms appear, in this order and spelled exactly: PayPal, Bomb Party, Shopify, Etsy, Ebay, SquareSpace, Wix, TikTok, WooCommerce, Maverick, Walmart. status is usually Active, Issues or Disconnected, or an empty string if never set. error is a connection error message, currently reported only for Walmart and otherwise null. last_sync is UTC, ISO-8601 without an offset, or null if the store has never synced; WooCommerce shows 0001-01-01T00:00:00 instead of null. With nothing connected: platforms: [], count: 0.

Good to know: One entry per platform, even if you've connected several stores on it. Square, Jotform and spreadsheet or email imports aren't listed. A Shopify store that revoked Ship.com's access can still appear, as Disconnected; reconnect it in Settings → Integrations before syncing. Read last_sync together with status: on a Disconnected Shopify entry it can be the time the problem was found rather than a successful sync.

sync_platform_orders

Changes data

Starts a background import of recent orders from your connected Shopify or Etsy store and returns right away with a job_id. A sync worker picks the job up, usually within seconds; follow it with get_sync_status. Importing only adds orders Ship.com doesn't already have, so running it again is safe.

ParameterTypeRequiredDescription
platformstringYesshopify or etsy. Not case-sensitive; surrounding spaces are ignored. Any other value, or none, returns an "Unknown platform" error.

Returns: job_id, platform (lowercase), status and a note saying whether a new job was queued or an existing one was returned. status is always queued, even when the returned job is already running.

Good to know: The platform must already be connected in Settings → Integrations; for Shopify the connection must also be active, not uninstalled or revoked. If a sync for that platform is already queued or running, including one started outside MCP such as the import that runs when you first connect a store, that job is returned instead of a second one. Shopify imports orders created in the last 7 days (30 days if the store has never completed a sync), plus new customers from the same window. Etsy imports orders from the last 7 days. Existing Ship.com orders aren't changed and nothing is written back to the store. Newly imported orders can trigger your own automation rules for imported orders and earn rewards points, like any other import. If the store has revoked Ship.com's access, the Shopify connection is marked disconnected or the Etsy connection is removed, and you'll need to reconnect it. No money moves and no email is sent. In the rare case the job can't be queued, you get "Could not queue the sync job"; try again in a moment.

{"platform": "shopify"}

get_sync_status

Read-only

Checks your background order-import jobs. Pass a job_id from sync_platform_orders to see that one job, or omit it to list your 10 most recent jobs, newest first.

ParameterTypeRequiredDescription
job_idintegerNoThe job to look up. Omit to list recent jobs. Send a whole number; a value that can't be read as a number returns "job_id must be an integer."; an ID that doesn't exist or belongs to another account returns a not-found error.

Returns: With job_id, that job's fields directly (no wrapper): job_id, service, platform, status (queued, running, completed or error), queued, started, completed, error and last_sync. Without it, jobs (up to 10 of those objects) and count. Times are UTC, ISO-8601 without an offset; started and completed are null until reached. error is free-text failure detail, or null. For Shopify and Etsy jobs, platform is shopify or etsy and last_sync is the connection's current last sync time, the same on every row for that platform rather than per job. For other jobs, platform repeats the service value and last_sync is null.

Good to know: The list covers all of your account's background import work, not only Shopify and Etsy: spreadsheet uploads, email-forwarded orders, other platforms and tracking refreshes appear too. completed means the job ran, not that orders arrived; an import that ran into a problem can still finish as completed with no error. To confirm orders landed, check that last_sync moved forward and the connection is still Active in list_connected_platforms, or list your orders. A Shopify or Etsy job still running about 10 minutes after it started, or one that hits a temporary failure, is closed with an error and retried as a new job, up to 5 times for stalls. The error text names the new job's ID, so an error row can have a newer follow-up above it. A job is marked completed about 10 seconds after its import finishes.

{"job_id": 73120458}

Customers and email

These tools work with your customer address book and let you reach customers by email. A typical flow: find someone with list_customers, read their details and order history with get_customer, fix their contact or address details with create_or_update_customer, then write to them with send_customer_email. For win-back campaigns, list_lapsed_customers finds customers who haven't ordered recently and send_mass_marketing_email emails a whole segment at once.

Sending email is a two-step flow. Call a send tool without confirm to preview it (nothing is sent), then call again with "confirm": true to send. Emails count against your plan's marketing-email allowance, and every tool here only sees your own customers. See Marketing-email allowance and Merge placeholders.

list_customers

Read-only

Lists the customers in your Ship.com address book, excluding deleted ones. Filter by name or email and sort alphabetically or by most recent order. Rows are compact; call get_customer for full details and order history. Unlike the email segment tools, this list includes customers who have no email address.

ParameterTypeRequiredDescription
querystringNoCase-insensitive filter. Matches customers whose first or last name starts with the text, or whose email contains it. One term only: "jane doe" isn't split into first and last name. Phone, city and company aren't searched.
sortstringNoname (default): by last name, then first name; customers with no last name come last. recent: customers with the newest orders first, ignoring canceled and merged orders; customers with no orders come last. Any other value sorts by name.
pageintegerNoPage number, starting at 1. Default 1.
page_sizeintegerNoRows per page, 1 to 50. Default 25. Values outside that range are clamped to it.

Returns: customers, each with customer_id, name, email, phone, city and state (null when not on file); plus page and page_size as applied, total (matches across all pages) and sort (the sort actually used).

Good to know: A page past the end returns an empty customers list with the real total. Customers that tie in the sort order have no guaranteed order among themselves.

{ "query": "jen", "sort": "recent", "page": 1, "page_size": 10 }

get_customer

Read-only

Returns the full record for one customer: name, company, contact details, birthday, mailing address, lifetime order count and spend, and their most recent orders.

ParameterTypeRequiredDescription
customer_idintegerYesThe customer's ID, from list_customers.

Returns: customer_id, name, first_name, last_name, company, email, phone, birthday, address (street1, street2, street3, city, state, zip, country), order_count, total_spent and recent_orders. Each recent order has order_id, order_number, date, status (for example "Ready to ship", "Label printed", "In transit", "Delivered"), total and tracking_number.

Good to know: Ship.com stores birthdays as month and day only, so birthday comes back with the year 1900 (for example "1900-04-12T00:00:00"); ignore the year. recent_orders lists up to 20 orders, newest first. order_count, total_spent and recent_orders leave out canceled and merged orders but include orders that haven't shipped yet; the count and total cover every such order, not just the 20 listed. order_number is null when an order has no number, and country is "US" when none is stored. A customer ID that isn't in your account returns an error such as Customer 482913 was not found in this account.

{ "customer_id": 482913 }

create_or_update_customer

Changes data

Adds a customer to your address book or updates one you already have. Omit customer_id (or pass 0) to create a customer; pass the ID of an existing customer to update them. Changes apply right away, with no confirm step.

ParameterTypeRequiredDescription
customer_idintegerNoThe customer to update. Omit or pass 0 to create a new customer. Deleted customers can't be edited.
first_namestringYesFirst name. Can't be blank.
last_namestringYesLast name. Can't be blank.
emailstringNoEmail address. The format isn't checked.
phonestringNoPhone number, stored as given.
birthdaystringNoMonth and day, month first, such as "04/12" or "4/12". No year is stored. Because the month comes first, "12/04" is December 4. A value with a year, a day-first date such as "25/12", February 29 or an empty string causes an error and nothing is saved.
street1stringNoAddress line 1. If it's blank and street2 is set, street2 moves up to line 1.
street2stringNoAddress line 2, such as an apartment or suite.
street3stringNoAddress line 3.
citystringNoCity.
statestringNoState or province code, such as "TX". Checked against the country's list of codes. A value that isn't a valid code (for example "Texas") is dropped without an error, so check address.state in the result.
zipstringNoZIP or postal code. Send it as a string so leading zeros are kept.
countrystringNoTwo-letter country code, such as US or CA. Default US.
companystringNoCompany name.

Returns: customer_id (new or existing), created (true when a customer was added), name, email, phone, company, birthday, address, orders_updated and note. Values are returned as stored, after the state check, line 2 promotion and country default. orders_updated counts orders changed to match (always 0 on create), and note summarizes them or is null.

Good to know: An update replaces the whole record. Any optional field you leave out is cleared, and country goes back to US. To change one field, read the customer with get_customer first, then send every field back with your change applied.

Every update, even one that doesn't change the name, also updates the customer's orders that don't have a label yet and still carry the name the customer had before the update. Those orders get the customer's new name, company, email, phone and address, so the next label prints the corrected details. Fields you left out are cleared on those orders too, so a partial update can wipe their shipping address. Orders with a purchased, printed or shipped label are never changed, and orders with a different ship-to name are left alone.

Creating a customer doesn't check for duplicates; creating someone who's already in your address book adds a second record. Missing names return first_name and last_name are required., and a customer_id that isn't in your account returns the not-found error described under get_customer.

{
  "customer_id": 482913,
  "first_name": "Jennifer",
  "last_name": "Alvarez-Reyes",
  "email": "jen.alvarez@example.com",
  "phone": "(512) 555-0142",
  "birthday": "04/12",
  "street1": "1208 Maple Ridge Dr",
  "street2": "Apt 4B",
  "city": "Austin",
  "state": "TX",
  "zip": "78704",
  "country": "US"
}

send_customer_email

Sends emailPreview, then confirmRequires: Marketing email

Sends one marketing email to one of your customers, under your name. Identify the recipient with either customer_id or order_id; the email address comes from that customer or order. The subject and message can use merge placeholders such as {First Name}, filled in for the recipient.

ParameterTypeRequiredDescription
customer_idintegerOne of these twoThe recipient customer's ID. Send this or order_id, not both.
order_idintegerOne of these twoAn order in your account, in any status. The email goes to the order's email address; orders saved without one fall back to the linked customer's address. Using an order also fills in order placeholders such as {Invoice Number}, {Tracking Link} and {Order Total}, and takes {First Name} and {Last Name} from the order's ship-to name.
subjectstringYesSubject line. Supports merge placeholders.
messagestringYesEmail body. HTML is allowed. Supports merge placeholders.
confirmbooleanNoOmit or false to preview; nothing is queued. true sends the email.

Returns: A preview returns preview (true), recipient_name, recipient_email, subject, limit_check and a note. A confirmed send returns queued (true), recipient (name, email), subject and a note. The subject is echoed as you wrote it, with placeholders not yet filled in. limit_check has allowed, reason, limit, used and message; the last four are null when sending is allowed (see Marketing-email allowance).

Good to know: The preview checks the recipient and your allowance; it doesn't render the email. With "confirm": true, the same checks run again, then the email is queued and goes out about a minute later. Placeholders are filled in when the email is queued. There's no scheduling option on this tool; use send_mass_marketing_email with scheduled_at for timed sends.

A preview isn't required before "confirm": true, and there's no duplicate protection: confirming twice sends two emails. No tool cancels a queued email. Every email includes an unsubscribe link, and addresses that have unsubscribed from Ship.com marketing emails are skipped. If your email settings copy you on emails you send, that applies here too.

Errors include Provide exactly one of customer_id or order_id to identify the recipient., subject and message are required., a not-found error for a customer or order that isn't in your account, and That recipient has no email address on file. If that last one appears for an order whose customer does have an email, send by customer_id instead. When your allowance is used up, the confirm call is refused with the allowance message.

{
  "customer_id": 482913,
  "subject": "A little thank-you, {First Name}",
  "message": "<p>Hi {First Name},</p><p>Thanks for being such a loyal customer! Use code THANKS10 for 10% off your next order.</p><p>{Your Name}</p>",
  "confirm": true
}

send_mass_marketing_email

Sends emailPreview, then confirmRequires: Marketing email

Sends one marketing email campaign to a segment of your customers, or to your whole emailable list. Filters narrow the audience by purchase history. This sends real email and can't be undone once sending starts, so always preview first and check the recipient count.

ParameterTypeRequiredDescription
filtersarray of stringsNoSegment filters, applied in order; each one narrows the list. Allowed values: "has purchased anything", "has not purchased anything", "has purchased product", "has not purchased product". Omit to email every customer with an email address.
daysarray of integersNoThe lookback window in days for each filter, matched by position: days[0] goes with filters[0]. Give a positive number for every filter. A missing entry counts as 0, which makes "has purchased anything" match no one and "has not purchased anything" match everyone.
productsarray of stringsNoThe product for each "...product" filter, matched by position. Case-insensitive partial match on the names of items ordered, such as "lavender". Ignored for the "...anything" filters. A missing or empty entry matches any item.
subjectstringYesSubject line. Supports merge placeholders.
messagestringYesEmail body. HTML is allowed. Supports customer placeholders; order placeholders don't apply to campaigns.
scheduled_atstringNoWhen to send, in ISO 8601, such as "2026-10-06T15:00:00Z". A time without an offset is read as UTC. Default: now. A value that can't be read also means now, so check scheduled_for in the result.
confirmbooleanNoOmit or false to preview; nothing is saved. true creates the campaign.

Returns: A preview returns preview (true), recipient_count, sample (up to 10 recipients, each with name, email and last_purchase), subject, limit_check and a note. A confirmed send returns queued (true), recipient_count, marketing_email_id (the campaign ID), scheduled_for (UTC) and a note.

Good to know: The audience is the customers in your address book that have an email address, one per address. When several customers share an identical address, the most recently added one is kept; differently capitalized addresses count as different. Customers without an email are never included. Any order counts as a purchase for the filters, including canceled ones.

Always pass filters, days and products as JSON arrays, even for a single filter. A value that isn't an array is ignored, and so is an unrecognized filter string. Ignored filters don't narrow anything, so the campaign could go to your whole list. Check recipient_count in the preview before you confirm.

On confirm, the segment is worked out again, so the count can differ from the preview. The campaign is refused if no one matches or if it would go over your remaining marketing-email allowance; there are no partial sends. The recipient list is fixed when you confirm. Each email goes to the customer's address as it stands at send time, deleted customers are skipped, and each customer gets at most one email per campaign. recipient_count can include addresses that have unsubscribed; they are skipped when sending.

A preview isn't required before "confirm": true, and there's no duplicate protection: confirming twice creates two campaigns. No tool cancels a campaign. Campaigns appear with your other email campaigns in the Ship.com web app. If your email settings copy you on emails you send, you get a copy of every email in the campaign.

{
  "filters": ["has not purchased anything", "has purchased product"],
  "days": [90, 365],
  "products": ["", "lavender"],
  "subject": "We miss you, {First Name}!",
  "message": "<p>Hi {First Name},</p><p>It's been a little while! Our new lavender collection just landed. Take 15% off with code COMEBACK15.</p><p>{Your Name}</p>",
  "scheduled_at": "2026-10-06T15:00:00Z",
  "confirm": true
}

list_lapsed_customers

Read-only

Finds win-back candidates: customers who haven't ordered in the last N days (60 by default). It sends nothing and doesn't use your marketing-email allowance. Pair it with send_mass_marketing_email or send_customer_email to reach them.

ParameterTypeRequiredDescription
daysintegerNoLookback window in days. Customers with no order in this window are listed. Default 60. Use a positive number.
productstringNoLists customers who haven't bought this product in the window instead (case-insensitive partial match on the names of items ordered). Customers who bought other things recently are included, so this is no longer a pure lapsed list.
pageintegerNoPage number, starting at 1. Default 1.
page_sizeintegerNoRows per page, 1 to 50. Default 20.

Returns: customers, each with customer_id, name, email and last_purchase (the date of their most recent order of any kind, or null); plus count (rows on this page), total (all matches), page, page_size and days.

Good to know: It uses the same audience rules as send_mass_marketing_email: only customers with an email address, one per address, most recently added customers first. Any order counts as a purchase, including canceled ones. total matches the recipient_count a campaign preview would show for the same segment: ["has not purchased anything"] with days [N], or ["has not purchased product"] with the same days and product. For mail or phone outreach, use list_customers, which includes customers without an email address.

{ "days": 90, "page": 1, "page_size": 25 }

Marketing-email allowance

Both send tools count against your plan's marketing-email allowance. Monthly usage is the number of marketing emails your account has sent in the current calendar month (UTC). A send that would go over the limit is refused as a whole; there are no partial sends.

PlanMarketing emails
Starter, and older plans no longer soldNot included
Growth1,000 per month
Executive25,000 per month
UltraUnlimited
Free trial10 in total for the whole trial (the plan's monthly limit also applies)

The allowance never blocks a preview; the preview reports the outcome in limit_check. When sending is blocked, allowed is false and reason and message say why; the confirm call is then refused with the same message. For trial_limit and tier_limit, limit and used show the cap and how much of it is used; for no_entitlement they are null.

reasonMessage
trial_limitYou have reached the maximum number of marketing emails allowed during your trial period.
tier_limitYou have reached the maximum number of marketing emails allowed with your subscription.
no_entitlementMarketing email campaigns are not included with your current subscription.

Merge placeholders

Placeholders work in both the subject and the message. Type them exactly as shown, including capitals, spaces and braces; anything not recognized is left as typed.

PlaceholderFills in with
{First Name}The customer's first name, or "there" when none is on file. When sending by order_id: the order's ship-to first name, or "Friend".
{Last Name}The customer's last name (the ship-to last name when sending by order_id).
{Your Name}, {Business Name}, {Sign-Off}Your name, your business name (or your name if none is set), and your saved email sign-off.
{Birthday}, {Birthday Short}, {Birthday Long}, {Birthday Month}The customer's birthday as 04/12, 4/12, April 12th or April. Empty when no birthday is on file.
{Reward Points Balance}, {Reward Points Remaining}, {Reward Points Required}, {Reward Text}, {Reward Icon}Details from your rewards program: the customer's points balance, points still needed, points required for a reward, the reward itself, and your rewards icon.
{Reward Message}A short rewards summary. Shown only on Executive and Ultra plans with an active rewards program, for customers with points history.
{Invoice Number}, {Tracking Link}, {Order Quantity}, {Order Total}, {Order Date Short}, {Order Date Long}, {Reward Points Earned}Order details. These fill in only with send_customer_email and order_id. Otherwise {Tracking Link} shows "Unknown", {Order Total} "$0.00", {Order Quantity} "0 items", and the rest are empty.

Account, credits, and reports

These tools cover the money and plan side of your account. Check your postage balance with get_balance and what your plan includes with get_subscription_info. See who you've referred with get_referral_info and move referral credit into postage with redeem_referral_credit. Then review spending, sales tax and shipping trends with get_shipping_report, get_tax_report and get_shipping_analytics.

The three reporting tools need a plan that includes business reporting, such as Executive; Starter and Growth don't include it. They always appear in tools/list, and the plan is checked on every call before the arguments are, so an account without the feature gets this error whatever it sends: "Your current Ship.com plan does not include business reporting. Upgrade the subscription to use this tool." Check tier.features.business_reporting from get_subscription_info first. Every tool here works only on your own account. Money is in US dollars as plain JSON numbers, and timestamps are UTC without a time zone suffix.

get_balance

Read-only

Returns your current postage balance and your auto-recharge setting. Check it before buying labels to explain costs and to see whether a purchase would take the balance below zero. With auto-recharge on, that tops the balance up from the payment method on file.

No parameters.

Returns: balance (postage balance in USD; can be negative), currency (USD), auto_recharge_amount (the minimum added each time the balance is topped up), auto_recharge_enabled (true when auto_recharge_amount is more than 0.01) and note, a one-line summary of what the auto-recharge setting means for label purchases.

Good to know: This is the same balance label purchases draw from, so it matches what a purchase will see. When the amount is $0.01 (pay as you go), a shortfall is charged per purchase plus a $0.30 card fee. At $0, auto-recharge is off: the $0.30 fee still applies, and a purchase your balance can't cover fails. auto_recharge_enabled is false in both cases.

get_subscription_info

Read-only

Returns your plan with its prices, limits and feature flags, plus your recent subscription payments. Call it first to check whether a plan-gated tool will run: business_reporting covers the reporting tools in this section, batch covers batch purchasing and carrier_pickup covers pickup scheduling.

No parameters.

Returns:

  • subscription_level: the plan on record, such as starter, growth or executive. Older accounts may show a legacy plan, and it is null when no plan is recorded.
  • is_annual: true for annual billing; null when the account has no subscription billing record.
  • tier: name, month_price, annual_price (full-year price), month_shipment_limit and month_email_limit (-1 means unlimited), platform_integration_limit, and features, a set of booleans: batch, business_reporting, year_end_reporting, tracking, carrier_pickup, api_access, ai_features, custom_integrations, email_support, priority_email_support, phone_support, chat_support and dedicated_support.
  • note: present only when tier is null.
  • recent_payments: up to 12 subscription payments, newest first. Each has date, reason, amount_due, amount_paid, refund (0 or a negative amount), charge_id (the payment reference, or unknown), reference_id, status (Succeeded or Pending), failed_reason and note.

Good to know: Legacy plans, and accounts with no plan recorded, return tier: null with a note that per-plan features and limits aren't available; plan-gated tools treat those accounts as not entitled. subscription_level doesn't reflect cancellation, and there is no cancellation field. Only batch, business_reporting and carrier_pickup decide whether MCP tools run; the other flags describe the plan, and api_access and ai_features don't restrict MCP access. Failed charges are left out, and several records for one charge are combined into one entry, with any refunds totaled in refund. On older records status can be null, and entries with charge_id unknown have an empty reference_id. No pagination.

get_referral_info

Read-only

Returns your referral credit balance and everyone you have referred, including their contact details, plan and what each referral has earned you. To spend the balance on postage, use redeem_referral_credit.

No parameters.

Returns: credit_balance (referral credit in USD), currency (USD) and referrals, one entry per referred account with name, email (may be null), signup_date (when they upgraded, or when they signed up if they haven't upgraded), subscription_level (their plan, such as growth), canceled (true if their account is canceled), has_paid (whether the one-time referral bonus for them has been paid out) and total_payments (referral credit earned from them; 0 if none).

Good to know: referrals lists every account you referred, canceled ones included, in no particular order, with no pagination; it is an empty list if you haven't referred anyone. name can be an empty string when a referred account is missing part of its name.

redeem_referral_credit

Changes dataPreview, then confirm

Moves your entire referral credit balance into your postage balance so you can spend it on labels. Call it without confirm to preview the two balances, then call again with confirm: true to make the move. It never charges a card or bank account.

ParameterTypeRequiredDescription
confirmbooleanNoOmit or send false to preview; nothing moves. Send the boolean true to redeem the whole referral balance. There is no amount parameter.

Returns: A preview returns preview: true, referral_credit_balance, shipping_balance (your postage balance) and a note. A confirmed call returns redeemed (the amount moved), referral_balance_after (normally 0), shipping_balance_after (your new postage balance) and a note such as "Moved $30.00 of referral credit into the postage balance."

Good to know: It always redeems the whole balance; partial redemption isn't supported. The preview and the confirm aren't linked, so the amount moved is the balance at the moment you confirm, which can differ from the preview. A preview with a $0 balance returns an error that there is nothing to redeem. Rarely, $0 means the balance couldn't be read, so try again later if you expected credit. Confirming at $0 succeeds with redeemed: 0 and moves nothing. The move can't be undone through MCP, and it appears as a referral credit in get_shipping_report. If a confirm reports that the postage balance did not increase, check both balances with get_balance and get_referral_info before trying again, and send one confirm at a time.

{"confirm": true}

get_shipping_report

Read-onlyRequires: Business reporting

Returns your shipping spend for one calendar year, broken down by month, plus a year total. It is built from your postage balance activity (label charges, credits and top-ups), not from orders.

ParameterTypeRequiredDescription
yearintegerYesCalendar year to report on, such as 2026. Months and the year boundary are in UTC.

Returns: year, months (one entry per month with activity, in calendar order) and total (the whole year). Every entry has these fields:

FieldTypeMeaning
datestringA date in that month, MM/dd/yyyy; not always the 1st. null on the total.
monthstringMonth name, such as January. TOTAL on the total.
shipmentsintegerOrders with at least one balance entry in the period that isn't marked canceled, each counted once.
postagenumberLabel postage, including any pay-as-you-go card fees, net of credits for canceled labels.
returnPostagenumberReturn label postage.
surchargenumberFirst Class surcharge.
upsAdjustmentsnumberNet UPS billing adjustments.
paymentsnumberAuto-recharge top-ups added to your balance.
refundsnumberCredits for canceled labels.
pendingnumberLabel charges whose cancellation is still awaiting a refund.
referralnumber or nullReferral credit moved into postage; null when none.
promotionalnumber or nullPromotional credits; null when none.
totalnumberNet spend: all charges minus all credits, not counting auto-recharge top-ups.

Good to know: Two keys are camelCase, returnPostage and upsAdjustments, unlike the snake_case used by other tools. postage already has refunds taken out, and the charges in pending are already counted in the charge fields, so don't apply either again. Other balance activity can also affect total, so it may not equal the sum of the named fields. The year's shipments counts each order once, so it can be lower than the sum of the months. A year with no activity returns an empty months list and a total of zeros, with referral and promotional null. Without year the tool returns "year is required."

{"year": 2026}

get_tax_report

Read-onlyRequires: Business reporting

Returns the sales tax you collected through paid Ship.com invoices, totaled by quarter and by month, across every year with paid invoices. Useful when filing sales tax.

No parameters.

Returns: taxes, an object whose keys are period labels and whose values are the sales tax collected in USD. Quarter keys look like Q3 2026 and month keys like September 2026. Keys run newest year first; within a year they run from December back to January, with each quarter just before its last month (Q4 2025, December 2025, November 2025, October 2025, Q3 2025, ...). Only periods with paid invoices appear, and taxes is {} when there are none.

Good to know: Tax is counted in the month and quarter the invoice was paid (UTC). Only orders with a paid Ship.com invoice are included, so orders from sales platforms that weren't invoiced through Ship.com aren't. A paid invoice still counts if its order was later canceled. When the current month has paid invoices, its key and every quarter key of the current year end in To Date (for example October 2026 To Date and Q2 2026 To Date); when it has none yet, no key does. Read the period from the start of the key and treat To Date as a label. There are no date filters.

get_shipping_analytics

Read-onlyRequires: Business reporting

Returns shipping analytics for a date range in one call: totals, daily order volume, order statuses, carrier mix, top destinations and sales channels. Orders are grouped by the day they were created (UTC). With no dates it covers the last 90 days through today.

ParameterTypeRequiredDescription
start_datestringNoFirst day of the range, YYYY-MM-DD, inclusive. Default: 90 days before end_date.
end_datestringNoLast day of the range, YYYY-MM-DD, inclusive; the whole day is counted. Default: today (UTC). Must be on or after start_date, and no more than 366 days after it.

Returns:

  • start_date, end_date: the range used, YYYY-MM-DD.
  • totals: orders (every order created in the range, any status), shipped (orders that currently have a purchased label) and postage_spent (the label prices on those shipped orders).
  • daily_volume: {date, orders} per day, oldest first. Days with no orders are left out.
  • status_distribution: {status, count}, using the status names from the order statuses table (such as ChooseShipping or Deleted), not the display labels.
  • carrier_mix: {carrier, count}. carrier is USPS, MoveMethod (USPS labels bought through a second Ship.com purchasing route; count them as USPS), UPS, unshipped or other (labels that match none of these, such as very old ones).
  • top_destinations: up to 10 {state, country, count} entries for the most common ship-to state and country. state is empty when missing, and a blank country reads US.
  • sales_channels: {channel, count}, where channel is the order's source as recorded, such as Shopify, Etsy, Square or Jotform. Orders created through the Ship.com REST API show a value starting with API-, orders with no recorded source show manual, and a few may show None.
  • truncated and note: present only when the range held more than 100,000 orders. Only the most recent 100,000 were counted; narrow the range for exact numbers.

Statuses, carriers and channels are sorted by count, highest first, with ties in name order; destinations break ties by state.

Good to know: No status filter is applied, so canceled and merged orders count toward every total and breakdown. list_orders, by contrast, filters by status. "Shipped" means the order holds a label right now: voiding a label moves the order back to unshipped. Destinations include unshipped orders, because they use each order's ship-to address. The 366-day limit allows up to 367 calendar days, counting both ends, and the default range spans 91 days. Errors: "end_date must be an ISO date (YYYY-MM-DD).", the same for start_date, "end_date must be on or after start_date." and "The date range is too large — keep it to 366 days or fewer." A future start_date with no end_date fails the on-or-after check.

{"start_date": "2026-07-01", "end_date": "2026-09-30"}

Changes and support

Versioning and changes

  • The server negotiates MCP protocol version 2025-11-25 or 2025-06-18 during initialize. If you ask for any other version, it answers with 2025-11-25.
  • Ship.com may add tools, add optional arguments and result fields, and improve tool descriptions and messages. Build clients to ignore fields they don't recognize.
  • tools/list is the source of truth for the tools and argument schemas available to your connection. The server doesn't send list-changed notifications, so read tools/list each time you connect instead of hard-coding the catalog.
  • This page describes the production server at https://app.ship.com/mcp. Where it and tools/list disagree, tools/list wins.

Your data

  • A connection can see and change only the Ship.com account its token belongs to.
  • Ship.com records each tool call's name, time and duration, and whether it failed. For failed calls only, it also keeps the error and the arguments sent, to help troubleshoot. These records are deleted after about 90 days. Arguments and results of successful calls aren't stored in this record.
  • OAuth access tokens, refresh tokens and authorization codes are stored only in hashed form.
  • Orders, labels, pickups and emails created through MCP are ordinary account activity and appear in the Ship.com web app like any other change.

Getting help

Email help@ship.com. To help us find the problem quickly, include the tool name, roughly when it happened (with time zone), whether you connect with an access token or OAuth sign-in, and the exact error message. Never send your token.