Skip to content
muswa.com

API reference · v1 · Professional

The Muswa APIfor scripts, SOAR and agents.

Everything the dashboard does, over HTTPS and MCP: investigate IPs, domains, networks, wallets and malware hashes, unmask origin servers hidden behind CDNs, and build shared investigation cases. Same engine, same quota, JSON in and out.

Basehttps://muswa.com/api/v1
openapi.json MCP setup
Endpoints
8
REST v1
MCP tools
8
Streamable HTTP
Check ids
103
6 modules
Requests / min
120
per API key
On this pageIntroduction
Getting started

Introduction

The API is a thin layer over the dashboard engine. A lookup validates a target and lists its checks; each check is one block of intelligence (location, abuse contact, certificates…). Start a lookup, then fetch checks in parallel or ask for all of them at once. Origin tracks find the real servers behind a CDN.
  1. 1

    Get a key

    Professional plan, then Dashboard → API. Export it as MUSWA_KEY.

  2. 2

    Start a lookup

    POST /lookup/{module} with a target. One lookup is charged; you get a lookupId and the check list.

  3. 3

    Collect results

    GET /lookup/{lookupId}?wait=1 runs every passive check server-side, or fetch /lookup/{lookupId}/{check} one by one. Both are free.

Format

JSON in, JSON out. UTF-8. Times are Unix milliseconds.

Errors

RFC 9457 problem+json with a stable machine code.

Versioning

Additive changes ship in v1. Breaking ones get /v2.

quickstart.sh
export MUSWA_KEY="mwk_live_…"

# 1. start a lookup (charges 1 lookup)
curl -X POST https://muswa.com/api/v1/lookup/domain \
  -H "Authorization: Bearer $MUSWA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target":"suspicious-login.example"}'

# 2. run every passive check in one call
curl "https://muswa.com/api/v1/lookup/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d?wait=1" \
  -H "Authorization: Bearer $MUSWA_KEY" --max-time 120
Getting started

Authentication

Send your key as a bearer token on every request. Keys belong to one account, act with its plan and quota, and can be revoked at any time from the dashboard. Cookies are ignored on the API: only the key counts.
Keys are shown once, at creation. Store them in a secret manager, never in front-end code or a public repository. If one leaks, revoke it: it stops working immediately.

Requirements

FieldDescription
AuthorizationreqBearer mwk_live_…headerKey created in Dashboard → API.
PlanreqProfessionalAPI and MCP are part of Professional. Other plans get 402 api_required.
Content-Typeapplication/jsonheaderRequired on POST requests with a body.
header
Authorization: Bearer mwk_live_…

401 missing or revoked key

{
  "type": "https://muswa.com/problems/unauthorized",
  "title": "Invalid or revoked API key",
  "status": 401,
  "ok": false,
  "error": "Invalid or revoked API key",
  "code": "unauthorized"
}
Getting started

Rate limits & quotas

Two independent budgets. Rate limits protect the service and reset every minute. The daily quota is your plan: only starting a lookup or an origin track spends it, reading results never does.

250lookups / day

Professional quota, shared with the dashboard. Resets 00:00 UTC.

5concurrent origin tracks

A slot frees itself when a track finishes, fails or times out.

Request limits (per minute)
ScopeLimitKeyed by
All /api/v1 endpoints120API key
POST /lookup/{module}30API key
GET /lookup/{lookupId}/{check}600API key
POST /origin10API key
MCP /mcp120API key
Everything under /api300client IP
Every response carries RateLimit (remaining r, seconds to reset t) and RateLimit-Policy. On 429 or 503, wait for Retry-After seconds before retrying.
response headers
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
RateLimit-Policy: "api-v1";q=120;w=60
RateLimit: "api-v1";r=117;t=42

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 18
Getting started

Errors

Errors are application/problem+json (RFC 9457). Branch on code; show title to people. A failing check is not an HTTP error: it answers 200 with ok: false.
StatusCodeWhenRetry
400invalid_targetThe target is not a valid IP, domain, ASN or wallet for this module. Nothing is charged.no
400bad_requestUnknown check id in checks, malformed parameters.no
400bad_jsonThe request body is not valid JSON.no
401unauthorizedMissing, malformed, invalid or revoked API key. Sent with WWW-Authenticate: Bearer.no
402api_requiredThe key owner has no active Professional plan.no
402plan_requiredNo active plan at all.no
404not_foundUnknown module, endpoint, lookup, check or track (or it belongs to someone else).no
409seek_limitEvery concurrent origin-track slot is busy. Wait for one to finish.no
410lookup_expiredThe lookup is older than 24 hours. Start a new one.no
429rate_limitedPer-key or per-IP request rate exceeded. Honour Retry-After.yes
429quota_exceededDaily lookup quota reached. Retry-After points to 00:00 UTC.yes
500internalSomething broke on our side. Safe to retry with backoff.yes
503unavailableOrigin scanning is temporarily unavailable. Nothing was charged.yes

429 application/problem+json

  • type"https://muswa.com/problems/quota_exceeded"
  • title"Daily limit of 250 lookups reached. It resets at 00:00 UTC."
  • status429
  • okfalse
  • error"Daily limit of 250 lookups reached. It resets at 00:00 UTC."
  • code"quota_exceeded"
GETFree/120 / min

Account and quota

Who the key belongs to, the active plan, today's lookup usage and running origin tracks. Call it first to check a key.
GET/api/v1/me

Response · 200

FieldDescription
okreqtrueAlways true on 2xx.
userreqobjectKey owner.
idreqstringUser id.
usernamereqstringDisplay name.
emailreqstringSign-in email.
apiKeyIdreqstringId of the key used (not the secret).
planreqobject | nullActive plan.
idreq"basic" | "professional"Plan id.
endsAtintegerUnix ms when the paid period ends.
quotareqobjectLookups today.
usedreqintegerUsed today (UTC).
limitreqintegerDaily cap.
resetsAtreqintegerUnix ms of the next 00:00 UTC.
originreqobjectOrigin-track slots.
runningreqintegerTracks running now.
limitreqintegerConcurrent tracks allowed.
curl "https://muswa.com/api/v1/me" \
  -H "Authorization: Bearer $MUSWA_KEY"
Response
{
  "ok": true,
  "user": {
    "id": "p-b-DtpqUg4McPphIiE85A",
    "username": "abuse-desk",
    "email": "[email protected]"
  },
  "apiKeyId": "key_mvlf2gkteac05c4975",
  "plan": {
    "id": "professional",
    "name": "Professional",
    "endsAt": 1824317850771
  },
  "quota": {
    "used": 25,
    "limit": 250,
    "resetsAt": 1792800000000
  },
  "origin": {
    "running": 1,
    "limit": 5
  }
}
GETFree/120 / min

Modules and checks

The catalogue of recon modules and their check ids, ordered by importance. Use it to build a checks filter or to render results in the same order as the dashboard.
GET/api/v1/modules

Response · 200

FieldDescription
okreqtrueAlways true on 2xx.
modules[]reqModule[]One entry per module.
idreq"ip" | "domain" | "asn" | "aml"Module id for POST /lookup/{module}.
labelreqstringDisplay name.
inputreqstringWhat the target looks like.
examplereqstringA sample target.
checks[]reqCheckDef[]Checks of this module.
idreqstringCheck id, used in URLs and in checks filters.
titlereqstringHuman title of the block.
weightreqintegerImportance 0-100. Higher renders first in the dashboard.
activebooleanTouches the target (probe). Only runs when explicitly requested.
slowbooleanUsually takes longer than 10 s.
curl "https://muswa.com/api/v1/modules" \
  -H "Authorization: Bearer $MUSWA_KEY"
Response
{
  "ok": true,
  "modules": [
    {
      "id": "ip",
      "label": "IP Recon",
      "input": "IPv4 or IPv6 address",
      "example": "185.199.108.153",
      "checks": [
        {
          "id": "geo",
          "title": "Location",
          "weight": 100,
          "active": false,
          "slow": false
        },
        {
          "id": "abuse",
          "title": "Abuse contact",
          "weight": 96,
          "active": false,
          "slow": false
        },
        {
          "id": "trace",
          "title": "Traceroute",
          "weight": 40,
          "active": true,
          "slow": true
        }
      ]
    }
  ]
}
POSTCharges 1 lookup/30 / min

Start a lookup

Validates and normalises the target, charges one lookup and returns the lookup id with the list of checks to run. Invalid targets are rejected before anything is charged.
POST/api/v1/lookup/{module}
  • A lookup stays usable for 24 hours: running or re-running its checks is free.
  • Then either fetch each check in parallel, or call GET /lookup/{lookupId}?wait=1 once.

Path parameters

FieldDescription
modulereq"ip" | "domain" | "asn" | "aml"pathWhich recon module to run.

Request body · application/json

FieldDescription
targetreqstringIP address, domain / hostname / URL, ASN (AS13335) or wallet address. Max 253 chars. Defanged input like evil[.]com is accepted.

Response · 201

FieldDescription
okreqtrueAlways true on 2xx.
lookupIdreqstringUUID v4 lookup id for the next calls.
modulereqstringModule id.
targetreqstringNormalised target (lower-cased, punycode, refanged).
targetTypestringipv4 · ipv6 · domain · subdomain · asn · evm · btc · tron · sol
checks[]reqCheckDef[]Checks available for this target, by weight.
idreqstringCheck id, used in URLs and in checks filters.
titlereqstringHuman title of the block.
weightreqintegerImportance 0-100. Higher renders first in the dashboard.
activebooleanTouches the target (probe). Only runs when explicitly requested.
slowbooleanUsually takes longer than 10 s.
quotareqobjectDaily lookup usage after this call.
usedreqintegerLookups used today (UTC).
limitreqintegerDaily limit of the plan.
curl -X POST "https://muswa.com/api/v1/lookup/ip" \
  -H "Authorization: Bearer $MUSWA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target":"185.199.108.153"}'
Response
{
  "ok": true,
  "lookupId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "module": "ip",
  "target": "185.199.108.153",
  "targetType": "ipv4",
  "checks": [
    {
      "id": "geo",
      "title": "Location",
      "weight": 100
    },
    {
      "id": "privacy",
      "title": "Privacy & classification",
      "weight": 99
    },
    {
      "id": "abuse",
      "title": "Abuse contact",
      "weight": 96
    }
  ],
  "quota": {
    "used": 26,
    "limit": 250
  }
}
GETFree/120 / min

Read a lookup (one-call mode)

Without wait, returns whatever checks already ran. With wait=1, runs every passive check that has no result yet on our side, with bounded concurrency, and returns all of them in a single response.
GET/api/v1/lookup/{lookupId}
  • The one-call mode answers within about 90 seconds. Checks still running at the deadline are listed in pending: fetch them individually a little later.
  • Active checks (probes that touch the target) only run when you list them in checks.
  • Set a client timeout of at least 100 s for wait=1.

Path parameters

FieldDescription
lookupIdreqstringpathUUID v4 id from POST /lookup/{module}.

Query parameters

FieldDescription
wait"1"queryRun missing checks before answering.
checksstringqueryComma-separated subset of check ids, e.g. geo,abuse,ptr.

Response · 200

FieldDescription
okreqtrueAlways true on 2xx.
lookupId / module / targetreqstringEcho of the lookup.
createdAtreqintegerUnix ms.
completebooleanwait=1 only: every requested check finished.
pendingstring[]wait=1 only: check ids still running at the deadline.
resultsreq{ [checkId]: CheckResult }One entry per finished check.
okreqbooleanfalse when the check failed. The lookup itself is still fine.
checkreqstringCheck id.
dataanyCheck-specific JSON. Stable per check id within v1.
errorstringReadable failure reason, present when ok is false.
emptybooleanRan fine, nothing found.
msreqintegerRun time in milliseconds.
cachedbooleanServed from our cache.
curl "https://muswa.com/api/v1/lookup/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d?wait=1" \
  -H "Authorization: Bearer $MUSWA_KEY" \
  --max-time 120
Response
{
  "ok": true,
  "lookupId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "module": "ip",
  "target": "185.199.108.153",
  "createdAt": 1792786962215,
  "complete": true,
  "pending": [],
  "results": {
    "geo": {
      "ok": true,
      "check": "geo",
      "data": {
        "city": "San Francisco",
        "countryCode": "US",
        "asn": 54113,
        "asName": "Fastly, Inc."
      },
      "empty": false,
      "ms": 184
    },
    "abuse": {
      "ok": true,
      "check": "abuse",
      "data": {
        "primary": {
          "name": "Abuse desk",
          "email": "[email protected]"
        }
      },
      "empty": false,
      "ms": 611
    },
    "ptr": {
      "ok": true,
      "check": "ptr",
      "empty": true,
      "ms": 42
    }
  }
}
GETFree/600 / min

Run one check

Runs (or re-runs) a single check of a lookup. This is what the dashboard fires in parallel for every block. A failing check still answers 200 with ok: false: HTTP errors are reserved for bad requests.
GET/api/v1/lookup/{lookupId}/{check}

Path parameters

FieldDescription
lookupIdreqstringpathUUID v4 lookup id.
checkreqstringpathCheck id from the lookup's checks list.

Query parameters

FieldDescription
*stringquerySome interactive checks accept extra parameters (max 300 chars each).

Response · 200

FieldDescription
okreqbooleanfalse when the check failed. The lookup itself is still fine.
checkreqstringCheck id.
dataanyCheck-specific JSON. Stable per check id within v1.
errorstringReadable failure reason, present when ok is false.
emptybooleanRan fine, nothing found.
msreqintegerRun time in milliseconds.
cachedbooleanServed from our cache.
curl "https://muswa.com/api/v1/lookup/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/geo" \
  -H "Authorization: Bearer $MUSWA_KEY"
Response
{
  "ok": true,
  "check": "geo",
  "data": {
    "ip": "185.199.108.153",
    "city": "San Francisco",
    "region": "California",
    "countryCode": "US",
    "lat": 37.7749,
    "lon": -122.4194,
    "asn": 54113,
    "asName": "Fastly, Inc.",
    "anycast": true
  },
  "empty": false,
  "ms": 184,
  "cached": false
}
POSTCharges 1 lookup/10 / min

Start an origin track

Looks for the real origin servers of a domain hidden behind a CDN or reverse proxy. Combines our daily internet-wide scan index (certificates, headers, banners and reverse DNS that leak the name) with an active probe of every HTTPS host we know.
POST/api/v1/origin
  • Charges one lookup and holds one concurrent-track slot until it finishes (Professional: 5).
  • A running track, or one that finished less than 1 hour ago, for the same domain is returned instead (reused: true, 200) and costs nothing. Send fresh: true to force a new one.
  • If the scanner is unavailable, or the track never leaves the queue, the lookup is refunded.

Request body · application/json

FieldDescription
domainreqstringDomain, hostname or URL.
freshbooleanIgnore a recent track for the same domain.

Response · 201

FieldDescription
okreqtrueAlways true on 2xx.
seekIdreqstringtrk_… id to poll.
domainreqstringNormalised domain.
reusedreqbooleanAn existing track was returned (not charged).
quotareqobjectDaily lookup usage after this call.
usedreqintegerLookups used today (UTC).
limitreqintegerDaily limit of the plan.
curl -X POST "https://muswa.com/api/v1/origin" \
  -H "Authorization: Bearer $MUSWA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}'
Response
{
  "ok": true,
  "seekId": "trk_mvlet9id616efa2dd3",
  "domain": "example.com",
  "reused": false,
  "quota": {
    "used": 27,
    "limit": 250
  }
}
GETFree/120 / min

Origin track results

Status and candidate origin IPs. Rows stream in while the probe runs and are sorted by confidence. Poll every few seconds, or long-poll with wait.
GET/api/v1/origin/{seekId}
  • phase goes queued → scanning → done (or failed / timeout). status collapses it to running / done / failed.

Path parameters

FieldDescription
seekIdreqstringpathtrk_… id.

Query parameters

FieldDescription
waitinteger 0-25querySeconds to wait for the track to finish before answering.

Response · 200

FieldDescription
id / domainreqstringTrack id and domain.
statusreq"running" | "done" | "failed"Coarse state.
phasereqenumqueued · scanning · done · failed · timeout
progressnumber 0-1Probe progress while scanning.
total / matched / originsreqintegerCandidates, probe matches, and likely origins (non-CDN, confidence ≥ 50).
rows[]reqOriginRow[]Up to 400 candidates.
ipreqstringCandidate address.
portintegerPort where the evidence was seen.
confidencereqinteger1-99 likelihood that this is the real origin.
matchedreqbooleanAnswered as the domain during the active probe.
cdnreqstring | nullCDN that owns the IP. Set means edge node, not origin.
asn / asName / countryinteger / string / stringNetwork owner and ISO country.
httpStatus / title / certCninteger / string / stringWhat the host served during the probe.
evidence[]reqEvidence[]Why this IP is a candidate.
kindreqenumhost_header · sni · default_cert · certificate · http · banner · ptr · other
detailreqstringReadable explanation.
hoststringName that leaked (can be a subdomain).
firstSeen / lastSeenintegerUnix ms, from our daily internet-wide scans.
errorstringWhy a track failed or timed out.
curl "https://muswa.com/api/v1/origin/trk_mvlet9id616efa2dd3?wait=20" \
  -H "Authorization: Bearer $MUSWA_KEY" \
  --max-time 120
Response
{
  "ok": true,
  "id": "trk_mvlet9id616efa2dd3",
  "domain": "example.com",
  "status": "done",
  "phase": "done",
  "progress": 1,
  "total": 2,
  "matched": 1,
  "origins": 1,
  "rows": [
    {
      "ip": "203.0.113.24",
      "port": 443,
      "confidence": 96,
      "matched": true,
      "cdn": null,
      "asn": 16276,
      "country": "FR",
      "httpStatus": 200,
      "certCn": "example.com",
      "evidence": [
        {
          "kind": "host_header",
          "detail": "Answered with the site for a Host header probe",
          "host": "example.com"
        },
        {
          "kind": "certificate",
          "detail": "Certificate SAN",
          "host": "example.com",
          "port": 443
        }
      ]
    },
    {
      "ip": "104.21.3.9",
      "port": 443,
      "confidence": 8,
      "matched": true,
      "cdn": "Cloudflare",
      "asn": 13335,
      "evidence": []
    }
  ]
}
GETFree/120 / min

List origin tracks

Your most recent tracks with their status, plus how many concurrent slots are in use.
GET/api/v1/origin

Query parameters

FieldDescription
limitinteger 1-100queryHow many tracks (default 30).

Response · 200

FieldDescription
seeks[]reqobject[]Newest first.
id / domainreqstringTrack id and domain.
statusreq"running" | "done" | "failed"Coarse state.
matchedreqintegerProbe matches.
createdAt / finishedAtintegerUnix ms.
runningreqintegerTracks running now.
limitreqintegerConcurrent tracks allowed.
curl "https://muswa.com/api/v1/origin?limit=10" \
  -H "Authorization: Bearer $MUSWA_KEY"
Response
{
  "ok": true,
  "seeks": [
    {
      "id": "trk_mvlet9id616efa2dd3",
      "domain": "example.com",
      "status": "running",
      "matched": 0,
      "createdAt": 1792786532251,
      "finishedAt": null
    }
  ],
  "running": 1,
  "limit": 5
}
Reference

Modules & check ids

Use these ids in GET /lookup/{lookupId}/{check}, in the checks filter, and in MCP tool calls. The live list is always at GET /modules. Active checks touch the target and only run when you ask for them.

input: IPv4 or IPv6 address · e.g. 185.199.108.153

  1. 01geoLocation
  2. 02localiseLatency geolocationactiveslow
  3. 03reputationReputation
  4. 04linkedDomains on this IP
  5. 05privacyPrivacy & classification
  6. 06abuseAbuse contact
  7. 07asnAutonomous system
  8. 08networkIP block
  9. 09exposureExposure
  10. 10blocklistsBlocklists
  11. 11routingRouting & RPKI
  12. 12companyCompany
  13. 13ptrReverse DNS
  14. 14confirmDomain confirmationactive
  15. 15portsPort & service scanactiveslow
  16. 16traceTracerouteactiveslow
  17. 17geotraceGeoTracerouteactiveslow
  18. 18openinOpen in
Agents

MCP server

Give Claude Code, Cursor or any MCP client the same tools as the dashboard. Endpoint https://muswa.com/mcp, Streamable HTTP, stateless, authenticated with your API key in the Authorization header. Tools that start a lookup charge it like the REST API.
  1. 1

    Create a key

    Dashboard → API. Keys start with mwk_live_ and are shown once.

  2. 2

    Register the server

    Paste the command or JSON for your client, replacing the key.

  3. 3

    Ask in plain words

    "Who hosts 185.199.108.153 and where do I report abuse?"

Tools
ToolArgumentsWhat it doesCost
ip_recontarget, checks?Every passive IP check: location, owner, abuse contact, reputation, blocklists, exposure.1 lookup
domain_recontarget, checks?DNS, registration, hosting and CDN, TLS, subdomains, phishing reputation, stack.1 lookup
asn_recontarget, checks?Owner, prefixes, neighbours, abuse contact, fraud signals.1 lookup
aml_screentarget, checks?Sanctions and illicit-exposure screening for a wallet.1 lookup
get_checklookupId, check, params?Full, untrimmed result of one check; runs active probes on demand.Free
origin_finddomain, waitSeconds?, fresh?Start an origin track and wait briefly for candidates.1 lookup
origin_statusseekId, waitSeconds?, limit?Poll an origin track.Free
account_usagenonePlan, quota used today, running tracks.Free
Recon tools return compact JSON (long lists trimmed) so they fit in an agent's context. Call get_check for the full result of one check, or for active probes.
terminal
claude mcp add --transport http muswa https://muswa.com/mcp \
  --header "Authorization: Bearer mwk_live_…"
JSON-RPC · tools/call
curl https://muswa.com/mcp \
  -H "Authorization: Bearer $MUSWA_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"origin_find","arguments":{"domain":"example.com"}}}'
Reference

Investigations & cases

A case groups the indicators that belong to one investigation — IPs, domains and wallets — so you can check them together, follow how they change and export the lot. The case endpoints are part of Professional and of every Team or Government organisation; people invited to a case who hold neither can read it, but cannot spend lookups on it.

The shape mirrors the rest of the API: JSON in and out, your key's plan and quota, results you can fetch without re-spending. A bulk check starts one lookup per indicator that still needs fresh data; re-reading a case never costs anything.

Representative endpoints
ActionShapeCost
Create a casePOST /casesFree
Add an indicatorPOST /cases/{id}/itemsFree
Bulk-check the casePOST /cases/{id}/check1 lookup / indicator
Read the timelineGET /cases/{id}/historyFree
Export (PDF or ZIP)GET /cases/{id}/exportFree

Shapes above are illustrative; the authoritative request and response schemas live in openapi.json.

The same actions are available over MCP: tools to look up a malware hash, resolve the origin behind a CDN, and create a case, add indicators to it or run it. Tools that start a lookup charge it, exactly like the REST API.
Reference

Changelog

v1.0.0
  • REST API v1 with API-key auth, RFC 9457 errors and RateLimit headers.
  • One-call mode: GET /lookup/{lookupId}?wait=1 runs every passive check server-side.
  • Origin tracks over the API: find real origin IPs behind CDNs.
  • MCP server at /mcp (Streamable HTTP) with 8 tools for AI agents.
  • OpenAPI 3.1 description at /openapi.json.

Questions or a missing field? Write to [email protected]. Found a vulnerability? See the bug bounty.