Surgemesh API documentation
The Surgemesh API is a REST and MCP interface at https://api.surgemesh.com for scheduling, publishing, listing and cancelling social posts on Facebook Pages and Instagram. Authenticate with "Authorization: Bearer sm_...", send JSON, and send an Idempotency-Key header when you create a post.
Base URL and authentication
- Base URL:
https://api.surgemesh.com - Auth header:
Authorization: Bearer sm_...(the key issm_followed by 40 characters). - Bodies: JSON. Money in responses is a JSON number in USD, for example
0.05. - Idempotency:
POST /v1/postsrequires anIdempotency-Keyheader (or anidempotency_keybody field). Other writes do not. - Errors:
application/problem+jsonwitherrorandhintfields. - Spec:
https://api.surgemesh.com/openapi.json.
Public endpoints with no key are GET /v1/pricing, GET /v1/quote and POST /v1/signup.
Quickstart in 4 steps
1. Sign up and get a sandbox key
curl -X POST https://api.surgemesh.com/v1/signup \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com", "accept_terms": true, "name": "Harbor Cafe", "brand": "Harbor Cafe"}'
{
"tenant_id": "t_k3m9q2x7p4w8",
"plan": "sandbox",
"brand_id": "b_r7t2kq",
"api_key": "sm_...",
"key_id": "<uuid>",
"note": "Key shown once. Sandbox validates posts but never publishes. Buy any credit pack (POST /v1/billing/topup) to go live.",
"next": ["GET /v1/me", "POST /v1/channels", "GET /v1/quote", "POST /v1/posts"]
}
The signup key has scopes read, write and admin and access to all brands (["*"]). name and brand are optional. accept_terms: true is required and accepts the Terms and Privacy Policy.
2. Connect a channel
curl -X POST https://api.surgemesh.com/v1/channels \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{
"brand_id": "b_r7t2kq",
"network": "instagram",
"label": "Harbor IG",
"credentials": { "token": "<Meta access token>", "ig_user_id": "17841400000000000" }
}'
{
"id": "ch_k2m9p4x7q3r8",
"brand_id": "b_r7t2kq",
"network": "instagram",
"connected": true,
"note": "Credentials are encrypted at rest and never returned."
}
For a Facebook Page, use "network": "facebook" and page_id instead of ig_user_id.
3. Get a quote
curl "https://api.surgemesh.com/v1/quote?scheduled_at=2026-11-03T14:00:00Z&deliveries=2"
{
"slot": "2026-11-03T14:00:00.000Z",
"deliveries": 2,
"multiplier": 1.25,
"price_micros": 50000,
"price_usd": 0.05,
"base_usd_per_delivery": 0.02,
"cheaper_slots": [
{ "slot": "2026-11-03T14:30:00.000Z", "multiplier": 0.8, "price_usd": 0.032 },
{ "slot": "2026-11-03T13:45:00.000Z", "multiplier": 1, "price_usd": 0.04 }
]
}
4. Create the post in the cheaper slot
curl -X POST https://api.surgemesh.com/v1/posts \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0d4b7f1e-5a2c-4e8b-b3f9-7c6a5d4e3f21" \
-d '{
"brand_id": "b_r7t2kq",
"channels": ["ch_fb...", "ch_ig..."],
"body": "Spring menu is live.",
"media_urls": ["https://cdn.example.com/spring.jpg"],
"scheduled_at": "2026-11-03T14:30:00Z",
"max_price_usd": 0.10
}'
{
"id": "<uuid>",
"gate_status": "pass",
"sandbox": false,
"slot": "2026-11-03T14:30:00.000Z",
"multiplier": 0.8,
"price_usd": 0.032,
"free_deliveries_used": 0
}
The response is 201 with an X-Price-Multiplier header. From a sandbox account the same request returns "sandbox": true: it is validated, never published and never charged.
Post rules
body: 1 to 5,000 characters.media_urls: up to 10, public HTTPS only. Instagram needs at least one image or video.channels: 1 to 20 channel ids, all belonging tobrand_id.scheduled_at: from 5 minutes in the past to 366 days ahead.max_price_usd: optional. If the locked price would exceed it, the API returns409 price_above_max.- Charging order: free monthly deliveries first, then prepaid credit. Failed deliveries are refunded to credit automatically.
List posts with GET /v1/posts?brand=b_r7t2kq. Each target has a state: queued, published, failed, cancelled or sandboxed.
MCP client configuration
The MCP server is stateless JSON-RPC 2.0 at POST https://api.surgemesh.com/mcp, with JSON responses (no SSE). It supports initialize (protocol version 2025-06-18), tools/list, tools/call and ping, and authenticates with the same Bearer header.
{
"mcpServers": {
"surgemesh": {
"url": "https://api.surgemesh.com/mcp",
"headers": { "Authorization": "Bearer sm_..." }
}
}
}
| Tool | Arguments | REST equivalent |
|---|---|---|
get_pricing | none | GET /v1/pricing |
get_quote | scheduled_at, deliveries | GET /v1/quote |
list_channels | none | GET /v1/channels |
list_brands | none | GET /v1/brands |
create_post | idempotency_key, brand_id, body, media_urls, scheduled_at, channels, max_price_usd | POST /v1/posts |
list_posts | brand_id | GET /v1/posts |
cancel_post | id | DELETE /v1/posts/{id} |
get_usage | none | GET /v1/usage |
In each tool result, content[0].text holds the REST JSON. On errors, isError is true and the JSON includes error and hint.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /v1/pricing | Public | Base price, dynamic pricing rule, emergency cap, packs, payment status |
| GET | /v1/quote | Public | Price for scheduled_at and deliveries |
| POST | /v1/signup | Public | Create a sandbox tenant, first brand and API key |
| GET | /v1/me | Key | Plan, status, credits, free deliveries left, key scopes |
| GET | /v1/usage | Key | Monthly deliveries and the credit ledger |
| GET | /v1/brands | Key | List brands |
| POST | /v1/brands | admin | Create a brand (max 25) |
| GET | /v1/channels | Key | List channels |
| POST | /v1/channels | admin | Connect a Facebook Page or Instagram account |
| DELETE | /v1/channels/{id} | admin | Disconnect a channel and erase its credentials |
| GET | /v1/posts | Key | List posts for a brand |
| POST | /v1/posts | write | Create a post (Idempotency-Key required) |
| DELETE | /v1/posts/{id} | write | Cancel queued deliveries and refund them |
| GET / POST | /v1/keys | admin | List or create API keys (max 50 active) |
| DELETE | /v1/keys/{id} | admin | Revoke a key |
| POST | /v1/billing/topup | admin | Buy a credit pack via x402 (not enabled in beta) |
| POST | /mcp | Key | MCP JSON-RPC endpoint |
Brands, channels and keys
A brand groups the channels of one business or client. A channel is one Facebook Page or one Instagram account. Create a brand with POST /v1/brands and {"name": "Harbor Cafe Catering"}, which returns 201 with {"id": "b_x8p3mn", "name": "Harbor Cafe Catering"}.
Mint a narrower key for each agent or client:
curl -X POST https://api.surgemesh.com/v1/keys \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{"name": "client-a-agent", "scopes": ["read", "write"], "brands": ["b_r7t2kq"], "expires_at": "2027-01-01T00:00:00Z"}'
{
"id": "<uuid>",
"key": "sm_...",
"scopes": ["read", "write"],
"brands": ["b_r7t2kq"],
"note": "Shown once. Store it in a secret manager."
}
A brand-restricted key cannot mint a "*" (all-brands) key. GET /v1/keys lists id, name, prefix, scopes, brands, created_at, last_used_at, expires_at and revoked, never the full key. DELETE /v1/keys/{id} returns {"revoked": "<uuid>"}.
Idempotency
POST /v1/posts requires an Idempotency-Key header, or an idempotency_key field in the body. Without it, the API returns 400 idempotency_key_required.
- Same key, same body: the original result with
"idempotent_replay": trueand HTTP200. No second post, no second charge. - Same key, different body:
409 idempotency_key_reused.
Other writes do not need a key. DELETE requests are naturally idempotent, and a top-up is protected because each on-chain payment can credit only once.
Errors
Errors use application/problem+json with a stable error code and a hint describing the next step. Validation errors add field:
{
"type": "https://surgemesh.com/errors/invalid-field",
"title": "Invalid field",
"status": 400,
"detail": "Invalid field: scheduled_at",
"error": "invalid_field",
"field": "scheduled_at",
"hint": "Fix the field named in `field` and retry. Schemas: GET /openapi.json."
}
| Status | error codes | What to do |
|---|---|---|
| 400 | invalid_field, invalid_json, idempotency_key_required, invalid_payment_header | Fix the request (see field) and resend |
| 401 | unauthorized, key_expired | Send a valid, unexpired Bearer key |
| 402 | insufficient_credit, payment_required, payment_invalid, payment_not_settled | Top up or fix the x402 payment, then retry |
| 403 | account_suspended, insufficient_scope, brand_not_allowed | Use a key with the right scope or brand; do not retry unchanged |
| 404 | brand_not_found, channel_not_found, not_found | Check the id |
| 409 | idempotency_key_reused, price_above_max, post_not_cancelable, email_taken | Read hint; requote, change slot or read post state |
| 413 | payload_too_large | Send a smaller body |
| 415 | json_required | Send Content-Type: application/json |
| 422 | invalid_media_url | Use a public HTTPS URL |
| 429 | rate_limited, sandbox_limit, signup_limited | For rate_limited, wait Retry-After seconds |
| 500 | internal_error | Retry with backoff; quote request_id if reporting |
| 503 | payments_not_enabled | Payments are off during the beta; keep using the sandbox |
Rate limits
- 60 requests per minute per IP address.
- 120 requests per minute per API key.
- An additional burst limit at the edge.
- Signup: 3 per IP address per day.
Exceeding a limit returns 429 rate_limited with a Retry-After header.
Top up credits with x402
Payments are not enabled during the beta. Today, POST /v1/billing/topup returns 503 payments_not_enabled. When payments are enabled, the flow is:
- Call
POST /v1/billing/topupwith{"pack": "pack_20"}("20"is also accepted) using an admin key. - Without an
X-PAYMENTheader, Surgemesh answers402with x402 requirements inaccepts. - Your x402 client signs a USDC payment on Base matching the requirement and sends it, base64-encoded, in
X-PAYMENT. - Surgemesh verifies and settles through an x402 facilitator into its Stripe deposit address, records the payment with Stripe, and answers
200with anX-PAYMENT-RESPONSEheader. A payment Stripe flags is held for review (202 payment_under_review). - Every 402 carries
terms_urlandterms_version. Sending payment means the operator behind the agent accepts the Terms.
{
"x402Version": 1,
"error": "payment_required",
"accepts": [{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "20000000",
"resource": "https://api.surgemesh.com/v1/billing/topup#pack_20",
"description": "Surgemesh credit pack_20: pay $20, get $22 credit",
"mimeType": "application/json",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"asset": "<USDC contract on Base>",
"extra": { "name": "USD Coin", "version": "2" }
}],
"hint": "..."
}
{ "credited_usd": 22, "credit_usd": 22, "plan": "live", "transaction": "0x..." }
A settled transaction can credit only once. The first top-up switches the account from sandbox to live.