# Spoofy — developer documentation > Growth for any link — followers, subscribers, members, likes, views, reactions, plays and site visits on ~35 platforms — from code or from an AI agent. Everything the Spoofy web app does is available here. Three ways in: - REST API: https://spoofy.social/api/v1 (OpenAPI 3.1 at https://spoofy.social/api/v1/openapi.json) - SMM-compatible endpoint: https://spoofy.social/api/v2 (key + action) - MCP server: https://spoofy.social/mcp (Streamable HTTP, stateless) Money is US dollars as decimal strings ("4.99"). Orders spend real balance. Errors are `{"error": {"code", "message", "details?"}}`. ## Quickstart ```bash # 1. Find a service (no key needed) — here: X followers curl "https://spoofy.social/api/v1/services?platform=x" # 2. Get the exact price for 1,000 (no key needed) curl -X POST https://spoofy.social/api/v1/quotes \ -H "Content-Type: application/json" \ -d '{"service": 12, "quantity": 1000}' # 3. Place the order — spends balance. Add "dry_run": true to check without charging. curl -X POST https://spoofy.social/api/v1/orders \ -H "Authorization: Bearer $SPOOFY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"service": 12, "link": "https://x.com/yourname", "quantity": 1000}' ``` Have a link rather than a platform? `GET /api/v1/links?url=` returns what the link points to and only the services that fit it. ## Authentication and safety rails Send `Authorization: Bearer fimi_live_…`. Keys are created and revoked by a signed-in person at https://spoofy.social/app/developers; the full key is shown once (we store a hash). An API key can never create keys or manage webhooks. Up to 20 active keys. - Read-only keys: `scopes: ["read"]` — browse, quote, read orders; never spend, refill or cancel. - Spend caps: `dailyCap` (per UTC day, net of refunds) and `perOrderCap`, USD strings. - Platform allow-list: `platforms: ["x", "tiktok"]`. - Dry runs: `"dry_run": true` on POST /api/v1/orders validates and prices, shows the balance, charges nothing. - Idempotency: send `Idempotency-Key` on every order; a retry with the same key returns the first order. - Orders made with a key are labelled with it in the dashboard. No key can withdraw money. ## API reference Base URL: https://spoofy.social ### Catalog What can be bought, for which link, at what price. No auth needed. #### GET /api/v1/services — List services The full public catalog: every service with its price per 1,000, range and package suggestions. Auth: no auth. Parameters: - `platform` (query): Only one platform, e.g. `x`, `tiktok`, `telegram`. ```bash curl "https://spoofy.social/api/v1/services?platform=x" ``` Response 200 (The catalog.): ```json { "services": [ { "id": 12, "platform": "x", "platformLabel": "X", "target": "profile", "metric": "followers", "metricLabel": "Followers", "tier": "standard", "name": "X Followers — Standard", "description": "Real-looking accounts, steady delivery, 30-day refill.", "linkHint": "https://x.com/yourname", "minQty": 100, "maxQty": 50000, "pricePer1k": "4.99", "pricePer1kMicros": "4990000", "refillDays": 30, "refillable": true, "cancelable": true, "startTime": "0–2 h", "speed": "2K / day", "tags": [ "refill" ], "startEstimate": "Usually starts within 1 h", "delivery": { "startMinutes": 60, "perDay": 2000, "avgMinutesPer1k": null }, "packages": [ { "quantity": 100, "price": "0.50", "eta": "~3 h", "etaMinutes": 132 }, { "quantity": 500, "price": "2.50", "eta": "~7 h", "etaMinutes": 420 }, { "quantity": 1000, "price": "4.99", "eta": "~13 h", "etaMinutes": 780 }, { "quantity": 5000, "price": "24.95", "eta": "~3 days", "etaMinutes": 3660 }, { "quantity": 50000, "price": "249.50", "eta": "~25 days", "etaMinutes": 36060 } ] } ] } ``` Errors: 500 internal. #### GET /api/v1/services/{id} — Get a service One service by id. Auth: no auth. Parameters: - `id` (path, required): Service id. ```bash curl "https://spoofy.social/api/v1/services/12" ``` Response 200 (The service.): ```json { "service": { "id": 12, "platform": "x", "platformLabel": "X", "target": "profile", "metric": "followers", "metricLabel": "Followers", "tier": "standard", "name": "X Followers — Standard", "description": "Real-looking accounts, steady delivery, 30-day refill.", "linkHint": "https://x.com/yourname", "minQty": 100, "maxQty": 50000, "pricePer1k": "4.99", "pricePer1kMicros": "4990000", "refillDays": 30, "refillable": true, "cancelable": true, "startTime": "0–2 h", "speed": "2K / day", "tags": [ "refill" ], "startEstimate": "Usually starts within 1 h", "delivery": { "startMinutes": 60, "perDay": 2000, "avgMinutesPer1k": null }, "packages": [ { "quantity": 100, "price": "0.50", "eta": "~3 h", "etaMinutes": 132 }, { "quantity": 500, "price": "2.50", "eta": "~7 h", "etaMinutes": 420 }, { "quantity": 1000, "price": "4.99", "eta": "~13 h", "etaMinutes": 780 }, { "quantity": 5000, "price": "24.95", "eta": "~3 days", "etaMinutes": 3660 }, { "quantity": 50000, "price": "249.50", "eta": "~25 days", "etaMinutes": 36060 } ] } } ``` Errors: 404 not_found. #### GET /api/v1/links — Inspect a link What a link points to (platform, profile/post/video/…) and every service that can grow it. Start here when you have a link. Auth: no auth. Parameters: - `url` (query, required): Any link, e.g. https://x.com/yourname. ```bash curl "https://spoofy.social/api/v1/links?url=https://x.com/yourname" ``` Response 200 (The link and the services that fit it.): ```json { "link": { "url": "https://x.com/yourname", "host": "x.com", "platform": "x", "target": "profile", "label": "@yourname" }, "services": [ { "id": 12, "platform": "x", "platformLabel": "X", "target": "profile", "metric": "followers", "metricLabel": "Followers", "tier": "standard", "name": "X Followers — Standard", "description": "Real-looking accounts, steady delivery, 30-day refill.", "linkHint": "https://x.com/yourname", "minQty": 100, "maxQty": 50000, "pricePer1k": "4.99", "pricePer1kMicros": "4990000", "refillDays": 30, "refillable": true, "cancelable": true, "startTime": "0–2 h", "speed": "2K / day", "tags": [ "refill" ], "startEstimate": "Usually starts within 1 h", "delivery": { "startMinutes": 60, "perDay": 2000, "avgMinutesPer1k": null }, "packages": [ { "quantity": 100, "price": "0.50", "eta": "~3 h", "etaMinutes": 132 }, { "quantity": 500, "price": "2.50", "eta": "~7 h", "etaMinutes": 420 }, { "quantity": 1000, "price": "4.99", "eta": "~13 h", "etaMinutes": 780 }, { "quantity": 5000, "price": "24.95", "eta": "~3 days", "etaMinutes": 3660 }, { "quantity": 50000, "price": "249.50", "eta": "~25 days", "etaMinutes": 36060 } ] } ] } ``` Errors: 400 bad_link. #### POST /api/v1/quotes — Get a quote The exact price of a quantity. Nothing is charged and no auth is needed. Auth: no auth. ```bash curl -X POST "https://spoofy.social/api/v1/quotes" \ -H "Content-Type: application/json" \ -d '{"service":12,"quantity":1000}' ``` Response 200 (The price.): ```json { "quote": { "service": 12, "quantity": 1000, "pricePer1k": "4.99", "total": "4.99", "currency": "USD" } } ``` Errors: 400 bad_quantity, 404 service_unavailable. ### Orders Place, read, refill and cancel orders. Spends balance. #### GET /api/v1/orders — List orders Your orders, newest first. Page with `before` = the smallest order number you have. Auth: API key (or the web session). Parameters: - `status` (query): Filter by status. - `limit` (query): 1–200, default 50. - `before` (query): Only orders with a smaller number. ```bash curl "https://spoofy.social/api/v1/orders" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 200 (Orders.): ```json { "orders": [ { "id": 1042, "service": { "id": 12, "name": "X Followers — Standard", "platform": "x", "metric": "followers" }, "link": "https://x.com/yourname", "quantity": 1000, "charge": "4.99", "refunded": "0.00", "status": "processing", "startCount": 1520, "delivered": 300, "remains": 700, "refillable": false, "cancelable": true, "source": "api", "createdAt": "2026-10-16T12:00:00.000Z", "finishedAt": null, "expectedAt": "2026-10-17T01:00:00.000Z" } ] } ``` Errors: 401 invalid_api_key. #### POST /api/v1/orders — Place an order Charges your balance and starts delivery. Send an `Idempotency-Key` header so a retry never double-charges. With `dry_run: true` nothing is charged: you get the price, your balance and whether it is enough. Auth: API key (or the web session). Parameters: - `Idempotency-Key` (header): Any unique string (≤ 120 chars) per intended order. Retrying with the same key returns the first order instead of charging twice. ```bash curl -X POST "https://spoofy.social/api/v1/orders" \ -H "Authorization: Bearer $SPOOFY_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"service":12,"link":"https://x.com/yourname","quantity":1000}' ``` Response 201 (Placed and charged.): ```json { "order": { "id": 1042, "service": { "id": 12, "name": "X Followers — Standard", "platform": "x", "metric": "followers" }, "link": "https://x.com/yourname", "quantity": 1000, "charge": "4.99", "refunded": "0.00", "status": "pending", "startCount": null, "delivered": null, "remains": null, "refillable": false, "cancelable": true, "source": "api", "createdAt": "2026-10-16T12:00:00.000Z", "finishedAt": null, "expectedAt": "2026-10-17T01:00:00.000Z" }, "replayed": false, "charged": "4.99" } ``` Response 200 (Dry run answer — or a replay of an earlier order with the same Idempotency-Key (`replayed: true`).): ```json { "dry_run": true, "quote": { "service": 12, "quantity": 1000, "total": "4.99", "currency": "USD" }, "balance": "12.00", "sufficient": true } ``` Errors: 400 invalid_body, 401 invalid_api_key, 402 insufficient_balance, 403 over_order_cap. #### GET /api/v1/orders/{id} — Get an order Status and progress of one order. Auth: API key (or the web session). Parameters: - `id` (path, required): Order number. ```bash curl "https://spoofy.social/api/v1/orders/1042" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 200 (The order.): ```json { "order": { "id": 1042, "service": { "id": 12, "name": "X Followers — Standard", "platform": "x", "metric": "followers" }, "link": "https://x.com/yourname", "quantity": 1000, "charge": "4.99", "refunded": "0.00", "status": "processing", "startCount": 1520, "delivered": 300, "remains": 700, "refillable": false, "cancelable": true, "source": "api", "createdAt": "2026-10-16T12:00:00.000Z", "finishedAt": null, "expectedAt": "2026-10-17T01:00:00.000Z" } } ``` Errors: 404 not_found. #### POST /api/v1/orders/{id}/refill — Request a refill For a completed order of a refillable service whose count dropped. Carried out in the background. Auth: API key (or the web session). Parameters: - `id` (path, required): Order number. ```bash curl -X POST "https://spoofy.social/api/v1/orders/1042/refill" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 202 (Requested.): ```json { "ok": true, "order": 1042, "action": "refill", "status": "requested" } ``` Errors: 400 not_refillable, 404 not_found. #### POST /api/v1/orders/{id}/cancel — Request a cancel For a pending or processing order of a cancelable service. Whatever was not delivered is refunded to your balance. Auth: API key (or the web session). Parameters: - `id` (path, required): Order number. ```bash curl -X POST "https://spoofy.social/api/v1/orders/1042/cancel" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 202 (Requested.): ```json { "ok": true, "order": 1042, "action": "cancel", "status": "requested" } ``` Errors: 400 not_cancelable, 404 not_found. #### POST /api/v1/uploads — Upload an image An image for a done-for-you order (logo, banner): multipart field `file`, or the raw bytes with an image Content-Type. PNG, JPG, WEBP or GIF up to 4 MB. Send the returned `id` in the order’s `details`; an upload no order uses is deleted after a day. Auth: API key (or the web session). ```bash curl -X POST "https://spoofy.social/api/v1/uploads" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 201 (Stored.): ```json { "upload": { "id": "cm2uploadlogo0000000000000", "mime": "image/png", "size": 48213, "width": 512, "height": 512 } } ``` Errors: 400 invalid_body, 401 invalid_api_key, 403 over_order_cap. ### Account Balance. #### GET /api/v1/balance — Get balance Your spendable balance in USD. Auth: API key (or the web session). ```bash curl "https://spoofy.social/api/v1/balance" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 200 (Balance.): ```json { "balance": "12.00", "currency": "USD" } ``` Errors: 401 invalid_api_key. ### Deposits Top up the balance with USDG (Robinhood Chain) or the project token (10 % off). #### GET /api/v1/deposits/methods — Deposit methods How to top up: your own deposit address per asset (USDG on Robinhood Chain; the project token at 10 % off once live). Auth: API key (or the web session). ```bash curl "https://spoofy.social/api/v1/deposits/methods" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 200 (Methods.): ```json { "methods": [ { "id": "usdg", "asset": "USDG", "chain": { "id": 4663, "name": "Robinhood Chain", "explorer": "https://rh-scan.com" }, "tokenAddress": "0x0000000000000000000000000000000000000001", "decimals": 6, "address": "0x1111111111111111111111111111111111111111", "treasury": "0x2222222222222222222222222222222222222222", "minimum": "1.00", "discountPct": 0, "priceUsd": null, "note": "Send any amount of USDG on Robinhood Chain to your address; it is credited 1 USDG = $1." } ] } ``` Errors: 401 invalid_api_key. #### GET /api/v1/deposits — List deposits Your deposits, newest first. Auth: API key (or the web session). ```bash curl "https://spoofy.social/api/v1/deposits" \ -H "Authorization: Bearer $SPOOFY_API_KEY" ``` Response 200 (Deposits.): ```json { "deposits": [ { "id": "cmkd3p0s1t0000qrs", "asset": "USDG", "amount": "25.00", "value": "25.00", "credit": "25.00", "txHash": "0xabababababababababababababababababababababababababababababababab", "status": "credited", "createdAt": "2026-10-16T11:58:00.000Z", "creditedAt": "2026-10-16T11:58:20.000Z" } ] } ``` Errors: 401 invalid_api_key. #### POST /api/v1/deposits/claim — Claim a wallet payment After paying the treasury from a wallet, send the transaction hash: it is verified on chain and credited. Auth: API key (or the web session). ```bash curl -X POST "https://spoofy.social/api/v1/deposits/claim" \ -H "Authorization: Bearer $SPOOFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"txHash":"0xabababababababababababababababababababababababababababababababab"}' ``` Response 200 (Credited.): ```json { "deposit": { "id": "cmkd3p0s1t0000qrs", "asset": "USDG", "amount": "25.00", "value": "25.00", "credit": "25.00", "txHash": "0xabababababababababababababababababababababababababababababababab", "status": "credited", "createdAt": "2026-10-16T11:58:00.000Z", "creditedAt": "2026-10-16T11:58:20.000Z" } } ``` Errors: 400 invalid_body, 401 invalid_api_key. ### API keys Create and revoke keys. Signed-in dashboard session only — a key cannot mint keys. #### GET /api/v1/keys — List API keys Every key of the signed-in user, revoked ones included. Secrets are never shown again. Auth: signed-in dashboard session only (an API key is refused). Response 200 (Keys.): ```json { "keys": [ { "id": "cmk1a2b3c0000xyz", "name": "Growth agent", "prefix": "fimi_live_Ab3x", "scopes": [ "read", "write" ], "dailyCap": "50.00", "perOrderCap": "10.00", "platforms": [ "x", "tiktok" ], "lastUsedAt": null, "createdAt": "2026-10-16T12:00:00.000Z", "revokedAt": null } ] } ``` Errors: 401 invalid_api_key, 403 session_required. #### POST /api/v1/keys — Create an API key Every field is optional. `scopes: ["read"]` makes a read-only key. Caps are USD strings; `platforms` limits which platforms the key may order for. The full key is in this answer only — store it now. Up to 20 active keys. Auth: signed-in dashboard session only (an API key is refused). Request body: ```json { "name": "Growth agent", "scopes": [ "read", "write" ], "dailyCap": "50.00", "perOrderCap": "10.00", "platforms": [ "x", "tiktok" ] } ``` Response 201 (Created. `key` is shown once.): ```json { "key": "fimi_live_Ab3x…(48 chars)", "apiKey": { "id": "cmk1a2b3c0000xyz", "name": "Growth agent", "prefix": "fimi_live_Ab3x", "scopes": [ "read", "write" ], "dailyCap": "50.00", "perOrderCap": "10.00", "platforms": [ "x", "tiktok" ], "lastUsedAt": null, "createdAt": "2026-10-16T12:00:00.000Z", "revokedAt": null } } ``` Errors: 400 invalid_body, 401 invalid_api_key, 409 too_many_keys. #### DELETE /api/v1/keys/{id} — Revoke an API key The key stops working at once. Auth: signed-in dashboard session only (an API key is refused). Parameters: - `id` (path, required): Key id. Response 200 (Revoked.): ```json { "ok": true } ``` Errors: 404 not_found. ### Webhooks Signed event delivery to your endpoint. Session only. #### GET /api/v1/webhooks — List webhooks Your active webhook endpoints. Auth: signed-in dashboard session only (an API key is refused). Response 200 (Webhooks.): ```json { "webhooks": [ { "id": "cmk9w8v7u0000abc", "url": "https://example.com/spoofy-webhook", "events": [ "order.updated" ], "active": true, "createdAt": "2026-10-16T12:00:00.000Z" } ] } ``` Errors: 401 invalid_api_key. #### POST /api/v1/webhooks — Add a webhook Public https URL; no `events` means every event. The signing `secret` is in this answer only. Up to 10 webhooks. Auth: signed-in dashboard session only (an API key is refused). Request body: ```json { "url": "https://example.com/spoofy-webhook", "events": [ "order.updated" ] } ``` Response 201 (Created.): ```json { "webhook": { "id": "cmk9w8v7u0000abc", "url": "https://example.com/spoofy-webhook", "events": [ "order.updated" ], "active": true, "createdAt": "2026-10-16T12:00:00.000Z", "secret": "whsec_…(shown once)" } } ``` Errors: 400 bad_webhook_url, 409 too_many_keys. #### DELETE /api/v1/webhooks/{id} — Remove a webhook Stops delivery to this endpoint; queued deliveries are dropped. Auth: signed-in dashboard session only (an API key is refused). Parameters: - `id` (path, required): Webhook id. Response 200 (Removed.): ```json { "ok": true } ``` Errors: 404 not_found. ### Meta This document. #### GET /api/v1/openapi.json — This document The OpenAPI 3.1 description of the API, for code generators and agents. Auth: no auth. ```bash curl "https://spoofy.social/api/v1/openapi.json" ``` ## Errors | HTTP | code | meaning | |---|---|---| | 400 | `invalid_body` | The JSON body is missing or has the wrong shape. The message says what to send. | | 400 | `bad_link` | The link is not a URL we can read. Send the full address, e.g. https://x.com/yourname. | | 400 | `wrong_platform` | The service is for another platform than the link (e.g. a TikTok service with an X link). | | 400 | `bad_quantity` | Quantity outside the service range. `details.min` / `details.max` hold the range. | | 400 | `not_refillable` | This order cannot be refilled (service has no refill, or the order is not completed). | | 400 | `not_cancelable` | This order cannot be cancelled now. | | 400 | `chain_not_supported` | The token is on a chain this service does not serve yet (GMGN live viewers: Robinhood Chain only). | | 400 | `details_required` | A done-for-you service needs a field of its `form`. `details.field` names it. | | 400 | `bad_details` | A `details` answer is not right (not a link, too long, an unknown or unsuitable upload). `details.field` names it. | | 400 | `no_file` | The upload carried no file. | | 400 | `bad_webhook_url` | Webhook URL must be public https://. | | 401 | `unauthorized` | No API key and no session. Send Authorization: Bearer fimi_live_…. | | 401 | `invalid_api_key` | The API key is wrong or revoked. | | 402 | `insufficient_balance` | Balance too low. `details.shortfall` is the USD amount to top up. | | 403 | `read_only_key` | This key has the read scope only and cannot place, refill or cancel orders. | | 403 | `platform_not_allowed` | The service's platform is not on this key's allow-list. | | 403 | `over_order_cap` | The order costs more than this key's per-order cap. | | 403 | `over_daily_cap` | The order would take this key over its daily spend cap (UTC day, net of refunds). | | 403 | `session_required` | Keys and webhooks are managed from the signed-in dashboard; an API key cannot manage them. | | 403 | `bad_origin` | A browser request from another site was refused. | | 404 | `not_found` | No such order / key / webhook on your account. | | 404 | `service_unavailable` | The service id does not exist or is paused right now. | | 409 | `too_many_keys` | You already have 20 active keys. Revoke one first. | | 409 | `too_many_webhooks` | You already have 10 webhooks. Remove one first. | | 409 | `link_busy` | As many as this service allows are already running on that link (GMGN: 120 live viewers per token). `details.available` is how many more fit now. | | 413 | `too_large` | The upload is over 4 MB. | | 415 | `not_an_image` | The upload is not a PNG, JPG, WEBP or GIF image. | | 429 | `too_many_uploads` | Too many uploads in the last hour. | | 500 | `internal` | Something failed on our side. Safe to retry; writes with the same Idempotency-Key never double-charge. | ## Webhooks Add an https endpoint in the dashboard; events `order.updated`, `deposit.credited`, `balance.low` (none picked = all). Each delivery is a POST of JSON `{ id, type, created, data }` with headers: - `Fimi-Signature: t=,v1=` — HMAC-SHA256 of `"."` with the `whsec_…` secret. - `Fimi-Event-Id` — the event id; delivery is at-least-once, drop duplicates. Answer 2xx within 10 s; otherwise retried with exponential backoff (1 min, 2, 4 … up to 6 h apart) for about 24 h. ```js import crypto from 'node:crypto'; // header = the Fimi-Signature request header; rawBody = the body exactly as received (a string, not re-serialised) export function verifySignature(header, rawBody, secret, toleranceSeconds = 300) { if (!header) return false; let t = null; const sigs = []; for (const part of header.split(',')) { const [k, v] = part.split('='); if (k === 't') t = Number(v); if (k === 'v1') sigs.push(v); } if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; // stale: possible replay const want = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); return sigs.some((s) => s.length === want.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(want))); } // Express: keep the raw body for this route app.post('/spoofy-webhook', express.text({ type: '*/*' }), (req, res) => { if (!verifySignature(req.get('Fimi-Signature'), req.body, process.env.SPOOFY_WEBHOOK_SECRET)) return res.sendStatus(400); const event = JSON.parse(req.body); // { id, type, created, data } // drop duplicates by event.id (also in the Fimi-Event-Id header): deliveries are at-least-once res.sendStatus(200); }); ``` Example `order.updated` body: ```json { "id": "evt_cmk5e6f7g0000hij", "type": "order.updated", "created": 1792152000, "data": { "order": { "id": 1042, "service": { "id": 12, "name": "X Followers — Standard", "platform": "x", "metric": "followers" }, "link": "https://x.com/yourname", "quantity": 1000, "charge": "4.99", "refunded": "0.00", "status": "processing", "startCount": 1520, "delivered": 300, "remains": 700, "refillable": false, "cancelable": true, "source": "api", "createdAt": "2026-10-16T12:00:00.000Z", "finishedAt": null, "expectedAt": "2026-10-17T01:00:00.000Z" } } } ``` ## SMM-compatible endpoint URL https://spoofy.social/api/v2; the same Spoofy API key, caps and prices. POST form-encoded (JSON and query string also work). Errors are `{"error": "…"}` with HTTP 200. | action | params | answer | |---|---|---| | services | | `[{service, name, type, category, rate, min, max, refill, cancel}]` — rate = USD per 1,000 | | add | service, link, quantity | `{order}` | | status | order | `{charge, start_count, status, remains, currency}` | | status | orders=1,2,3 (≤100) | `{"1": {…}, "2": {"error": …}}` | | refill | order or orders | `{refill}` or `[{order, refill}]` | | cancel | order or orders | `{order, cancel}` or `[{order, cancel}]` | | balance | | `{balance, currency: "USD"}` | Status words: Pending, Processing, In progress, Completed, Partial, Canceled. `charge` is net of refunds. ```bash curl -X POST https://spoofy.social/api/v2 -d key=$SPOOFY_API_KEY -d action=services curl -X POST https://spoofy.social/api/v2 -d key=$SPOOFY_API_KEY -d action=add -d service=12 -d link=https://x.com/yourname -d quantity=1000 curl -X POST https://spoofy.social/api/v2 -d key=$SPOOFY_API_KEY -d action=status -d order=1042 curl -X POST https://spoofy.social/api/v2 -d key=$SPOOFY_API_KEY -d action=status -d orders=1042,1043 curl -X POST https://spoofy.social/api/v2 -d key=$SPOOFY_API_KEY -d action=balance ``` ## AI agents (MCP) Remote MCP server at https://spoofy.social/mcp, Streamable HTTP, stateless, auth `Authorization: Bearer fimi_live_…`. Give an agent a key with a daily cap and a per-order cap. ### Claude Code Run in a terminal: ```bash claude mcp add --transport http spoofy https://spoofy.social/mcp --header "Authorization: Bearer YOUR_KEY" ``` ### Cursor Add to .cursor/mcp.json in your project (or ~/.cursor/mcp.json for every project): ```json { "mcpServers": { "spoofy": { "url": "https://spoofy.social/mcp", "headers": { "Authorization": "Bearer YOUR_KEY" } } } } ``` ### Claude Desktop Settings → Developer → Edit Config (claude_desktop_config.json), then restart Claude. Needs Node.js: ```json { "mcpServers": { "spoofy": { "command": "npx", "args": [ "-y", "mcp-remote", "https://spoofy.social/mcp", "--header", "Authorization:${SPOOFY_AUTH}" ], "env": { "SPOOFY_AUTH": "Bearer YOUR_KEY" } } } } ``` ### VS Code (Copilot agent mode) Add to .vscode/mcp.json: ```json { "servers": { "spoofy": { "type": "http", "url": "https://spoofy.social/mcp", "headers": { "Authorization": "Bearer YOUR_KEY" } } } } ``` ### Any MCP client Streamable HTTP transport, stateless (no session needed): ```bash URL: https://spoofy.social/mcp Header: Authorization: Bearer YOUR_KEY Method: POST JSON-RPC 2.0 (Accept: application/json, text/event-stream) ``` ### Tools - `find_services`: Find what Spoofy can do for a link or a goal, with prices. ALWAYS call this first: service ids come from here. Give `link` (any profile, post, video, channel, group, token page or website URL) and/or `goal` in plain words, e.g. "500 followers on my tiktok" or "cheap views for this YouTube video". With a link you only get services that fit that exact link (a profile link gets followers, a post link gets likes/views). Returns matching services: `service` (the id to quote/order), name, tier (basic/standard/premium), pricePer1k (USD per 1,000 units), min/max quantity, start time, speed, refill days, preset packages with USD prices, and — when the goal names a quantity — a `suggestion` with the exact total for that quantity. Free; spends nothing. - `get_quote`: Exact price in USD for a quantity of one service (from find_services). Checks the quantity is within the service range. Free; spends nothing. Use it to tell the user the cost before place_order. - `place_order` (acts): Place an order. THIS SPENDS THE USER'S REAL BALANCE (USD) and starts delivery to the link; it cannot be undone except by cancel_order where the service allows it. Before calling: get the price with get_quote and get the user's explicit agreement to that price. Then call with `confirm: true`. Without `confirm: true` nothing is charged and you get the price back so you can ask. `dry_run: true` validates everything (link fits the service, quantity range, key limits) and returns price, balance and whether it is enough — never charges. Pass a fresh `idempotency_key` per intended order so a retry never charges twice. Errors: insufficient_balance (details.shortfall = USD to top up — call get_top_up_info), over_order_cap / over_daily_cap / platform_not_allowed / read_only_key (this key's limits), wrong_platform, bad_quantity, link_busy (too many running on that link: details.available), chain_not_supported. Done-for-you services (kind "manual": listings) need a form with images — those are ordered on the website. - `get_order`: Status and progress of one order: status (pending, processing, completed, partial, refunded, cancelled, failed), start count, delivered, remains, charge and refunds in USD, and whether refill/cancel are possible now. - `list_orders`: The user's orders, newest first. Filter by status; page with `before` = the smallest order number already seen. - `request_refill` (acts): Ask for a refill of a completed order whose count dropped (only when the order says refillable: true). Free. Carried out in the background; check progress with get_order. - `cancel_order` (acts): Ask to cancel a pending or processing order (only when the order says cancelable: true). Whatever was not delivered is refunded to the balance in USD. Confirm with the user first. - `get_balance`: The user's spendable balance in USD. Orders are paid from it. - `get_top_up_info`: How the user adds money to the balance: their own deposit address for USDG on Robinhood Chain (credited 1 USDG = $1) and, once live, the project token at 10 % off, plus the top-up page link. Use it when an order fails with insufficient_balance. You cannot move money yourself — give the user the address or the link. Raw JSON-RPC: ```bash curl -X POST https://spoofy.social/mcp \ -H "Authorization: Bearer $SPOOFY_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"find_services","arguments":{"goal":"500 followers","link":"https://x.com/yourname"}}}' ``` ## TypeScript SDK (@fimi/sdk) Zero dependencies, fetch-based; Node 18+, Bun, Deno, browsers, edge. `orders.create` sends an auto-generated idempotency key. Methods: services.list/get, links.inspect, quotes.create, orders.create/list/get/refill/cancel, balance.get, deposits.methods/list/claim, webhooks.verify(signatureHeader, rawBody, secret). ```ts import { Spoofy } from '@fimi/sdk'; const spoofy = new Spoofy({ apiKey: process.env.SPOOFY_API_KEY }); const { link, services } = await spoofy.links.inspect('https://x.com/yourname'); const service = services.find((s) => s.metric === 'followers')!; const quote = await spoofy.quotes.create({ service: service.id, quantity: 1000 }); const check = await spoofy.orders.create({ service: service.id, link: link.url, quantity: 1000, dry_run: true }); if (check.sufficient) { const { order } = await spoofy.orders.create({ service: service.id, link: link.url, quantity: 1000 }); console.log(`#${order.id} ${order.status} — charged $${order.charge} (quoted $${quote.total})`); } ```