{"openapi":"3.1.0","info":{"title":"AbuseScore API","version":"1.0.0","summary":"Browser identification and abuse signals.","description":"Two keys, and the difference between them is the whole security model.\n\nA **publishable key** (`pk_live_...`) goes in browser JavaScript. It is in the page source of\nevery site that embeds the library and anybody can read it, so it is restricted to the origins\nyou register and rate limited. It can identify a browser and nothing else.\n\nA **secret key** (`sk_live_...`) is server to server. It reads a verdict back.\n\nThat split matters more than it looks. The answer the browser receives is an answer the person\nholding that browser can also fake, because they control it. Only the answer your server\nfetches with the secret key is worth making a decision on. Take the visitor id from the page,\nsend it to your own backend, and check it here.","contact":{"name":"Support","email":"support@abusescore.com","url":"https://abusescore.com/developers"},"license":{"name":"Terms of Service","url":"https://abusescore.com/legal/terms"}},"servers":[{"url":"https://abusescore.com","description":"Production"}],"tags":[{"name":"Lookup","description":"What is known about an IP address, and how risky it is."},{"name":"Fingerprinting","description":"Recognise a browser across visits, including after it clears its storage."}],"paths":{"/v1/lookup/{ip}":{"get":{"tags":["Lookup"],"operationId":"lookupAddress","summary":"Score one address","description":"Everything known about an address, and a risk score from 0 to 100 with the reason codes\nthat produced it.\n\nWhich fields come back depends on your plan. Every tier is a superset of the one below it,\nso upgrading never makes a field disappear. `GET /v1/me` lists exactly what yours includes.\n\n**A score of 0 with no reasons is a real answer**, not a miss. Most of the address space is\non no list at all, and that is worth knowing.\n\nPrivate, loopback, link-local and reserved addresses are refused rather than scored. One\narriving here almost always means you are reading your own load balancer's address instead\nof your visitor's, and a confident \"clean\" would let you ship that mistake.","security":[{"SecretKey":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"45.66.1.9"}],"responses":{"200":{"description":"The answer, filtered to your plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"},"example":{"ip":"45.66.1.9","version":4,"score":60,"band":"medium","is_bogon":false,"country":"SC","continent":"AF","flags":["tor_exit","hosting"],"asn":64496,"as_name":"Example Networks","as_domain":"example.net","network":"hosting","prefix":"45.66.0.0/16","reasons":[{"code":"TOR_EXIT","weight":55,"category":"anonymity","detail":"Traffic leaves the Tor network here. Whoever sent it is deliberately anonymous."},{"code":"HOSTING_ASN","weight":30,"category":"anonymity","detail":"A datacentre, not a home or an office. People browse from homes."}],"categories":{"anonymity":{"raw":85,"cap":60,"applied":60}},"sources":2,"data_age_seconds":3421}}}},"401":{"description":"Send a secret key. Publishable keys cannot make lookups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_key","message":"Send a secret key. Publishable keys cannot make lookups.","requestId":"9f1c0a7b23d45e68"}}}}},"402":{"description":"The allowance is used up, or the subscription is not active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"quota_exhausted","message":"The allowance is used up, or the subscription is not active.","requestId":"9f1c0a7b23d45e68"}}}}},"422":{"description":"Not an address, or not a public one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_address","message":"Not an address, or not a public one.","requestId":"9f1c0a7b23d45e68"}}}}},"429":{"description":"Over the per-second rate limit. Slow down and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Over the per-second rate limit. Slow down and retry.","requestId":"9f1c0a7b23d45e68"}}}}},"503":{"description":"Temporarily unavailable. Retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unavailable","message":"Temporarily unavailable. Retry.","requestId":"9f1c0a7b23d45e68"}}}}}}}},"/v1/lookup":{"post":{"tags":["Lookup"],"operationId":"lookupAddresses","summary":"Score several addresses","description":"One round trip instead of five hundred. The limit per request is your plan's `bulk_max`;\nthe Free plan has none.\n\nThe whole batch is charged against your allowance before any of it is answered, so you\nnever get a partial result you have already paid for. A malformed entry does not fail the\nbatch: it comes back as an object with an `error` instead of a score.","security":[{"SecretKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkRequest"},"example":{"ips":["45.66.1.9","8.8.8.8","2001:4860:4860::8888"]}}}},"responses":{"200":{"description":"The answers, in the order they were sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResponse"},"example":{"results":[{"ip":"45.66.1.9","version":4,"score":60,"band":"medium","is_bogon":false,"country":"SC","continent":"AF","flags":["tor_exit","hosting"],"asn":64496,"as_name":"Example Networks","as_domain":"example.net","network":"hosting","prefix":"45.66.0.0/16","reasons":[{"code":"TOR_EXIT","weight":55,"category":"anonymity","detail":"Traffic leaves the Tor network here. Whoever sent it is deliberately anonymous."},{"code":"HOSTING_ASN","weight":30,"category":"anonymity","detail":"A datacentre, not a home or an office. People browse from homes."}],"categories":{"anonymity":{"raw":85,"cap":60,"applied":60}},"sources":2,"data_age_seconds":3421},{"ip":"nonsense","error":"\"nonsense\" is not an IP address."}],"count":2}}}},"400":{"description":"The body is not {\"ips\": [...]}.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_body","message":"The body is not {\"ips\": [...]}.","requestId":"9f1c0a7b23d45e68"}}}}},"401":{"description":"Send a secret key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_key","message":"Send a secret key.","requestId":"9f1c0a7b23d45e68"}}}}},"402":{"description":"The allowance is used up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"quota_exhausted","message":"The allowance is used up.","requestId":"9f1c0a7b23d45e68"}}}}},"403":{"description":"This plan does not include bulk lookups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"bulk_not_included","message":"This plan does not include bulk lookups.","requestId":"9f1c0a7b23d45e68"}}}}},"422":{"description":"More addresses than the plan accepts in one request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"bulk_too_large","message":"More addresses than the plan accepts in one request.","requestId":"9f1c0a7b23d45e68"}}}}},"429":{"description":"Over the per-second rate limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Over the per-second rate limit.","requestId":"9f1c0a7b23d45e68"}}}}}}}},"/v1/me":{"get":{"tags":["Lookup"],"operationId":"getAccount","summary":"Your plan, your quota, your fields","description":"What this key can do and how much of it is left. **Costs nothing against your allowance**,\nso an integration can check it on boot and a human can check it while debugging a quota\nproblem without spending quota to find out what their quota is.","security":[{"SecretKey":[]}],"responses":{"200":{"description":"Your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"},"example":{"plan":{"id":"basic","name":"Basic","status":"active","active":true,"field_tier":"basic","fields":["ip","version","score","band","is_bogon","country","continent","flags","asn","as_name","as_domain","network","prefix","reasons"],"bulk_max":500,"rate_limit_per_second":100,"attribution_required":false,"current_period_end":"2026-10-14T00:00:00+00:00"},"usage":{"period":"month","used":84210,"allowance":1000000,"remaining":915790,"resets_at":"2026-10-01T00:00:00+00:00"},"credits":0,"data":{"age_seconds":3421,"ranges":{"v4":4182993,"v6":221044}}}}}},"401":{"description":"Send a secret key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_key","message":"Send a secret key.","requestId":"9f1c0a7b23d45e68"}}}}}}}},"/v1/fingerprint":{"post":{"tags":["Fingerprinting"],"operationId":"identifyBrowser","summary":"Identify a browser","description":"Called by the browser library. You will not normally call this yourself.\n\nTakes the signals collected on the page and returns the visitor id they belong to, creating\none if this browser has not been seen before. Signals the browser could not measure\nreliably are listed in `unstable` and excluded from the comparison on both sides, which is\nwhat keeps a browser that randomises its canvas from becoming a new visitor every session.\n\n**Do not make decisions on this response.** It was produced from data the browser sent and\ndelivered to the browser, and anyone can edit both. Use it to obtain the visitor id, send\nthat to your own server, and verify it with `GET /v1/fingerprint/{visitorId}`.","security":[{"PublishableKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentifyRequest"},"example":{"key":"pk_live_7QZ3M8XKD2VN0BHR5TJWY4FC61PSAE9G","components":{"canvas":"e3b0c44298fc1c149afbf4c8996fb924","webgl":"9f86d081884c7d659a2feaa0c55ad015","fonts":"2c26b46b68ffc68ff99b453c1d304134","audio":"fcde2b2edba56bf408601fb721fe9b5c","screen":"486ea46224d1bb4fb680f34f7c9ad96a","timezone":"6b51d431df5d7f141cbececcf79edf3d","platform":"d4735e3a265e16eee03f59718b9b5d03"},"unstable":[],"visitorId":"5f2a9c1e7b4d3086af51cc92e0b7d143","incognito":false,"bot":"","privacy":"","tag":"checkout"}}}},"responses":{"200":{"description":"The browser was identified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentifyResponse"},"example":{"visitorId":"5f2a9c1e7b4d3086af51cc92e0b7d143","confidence":94,"matchedBy":"storage","similarity":98,"returning":true,"firstSeen":"2026-06-02T09:14:07+00:00","lastSeen":"2026-09-14T11:02:55+00:00","sightings":37,"incognito":false,"bot":"","privacy":"","ip":"203.0.113.42","ipVersion":4,"signals":{"collected":19,"weight":541,"maximum":586,"unstable":[]}}}}},"401":{"description":"No active publishable key matches the one presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_key","message":"No active publishable key matches the one presented.","requestId":"9f1c0a7b23d45e68"}}}}},"403":{"description":"The key is not registered for this origin.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"origin_not_allowed","message":"The key is not registered for this origin.","requestId":"9f1c0a7b23d45e68"}}}}},"413":{"description":"The body is larger than the endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"body_too_large","message":"The body is larger than the endpoint accepts.","requestId":"9f1c0a7b23d45e68"}}}}},"422":{"description":"The browser revealed too little to identify anything.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_signals","message":"The browser revealed too little to identify anything.","requestId":"9f1c0a7b23d45e68"}}}}},"429":{"description":"Over the rate limit. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Over the rate limit. See Retry-After.","requestId":"9f1c0a7b23d45e68"}}}}},"503":{"description":"Temporarily unavailable. Retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unavailable","message":"Temporarily unavailable. Retry.","requestId":"9f1c0a7b23d45e68"}}}}}}}},"/v1/fingerprint/{visitorId}":{"get":{"tags":["Fingerprinting"],"operationId":"getVisitor","summary":"Read a verdict back, server side","description":"**This is the call that means something.** Your page hands your server a visitor id; your\nserver asks here with a key the page never had. A browser can claim any id it likes and it\ncannot make this request.\n\nReturns what we know about the visitor, including the history of sightings, so you can see\nwhether an id that claims to be three months old actually is.","security":[{"SecretKey":[]}],"parameters":[{"name":"visitorId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"},"example":"5f2a9c1e7b4d3086af51cc92e0b7d143"}],"responses":{"200":{"description":"The visitor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Visitor"},"example":{"visitorId":"5f2a9c1e7b4d3086af51cc92e0b7d143","confidence":94,"firstSeen":"2026-06-02T09:14:07+00:00","lastSeen":"2026-09-14T11:02:55+00:00","sightings":37,"incognito":false,"bot":"","privacy":"","history":[{"at":"2026-09-14T11:02:55+00:00","matchedBy":"storage","similarity":98,"confidence":94,"ip":"203.0.113.42","origin":"https://shop.example.com","incognito":false,"bot":"","privacy":"","tag":"checkout"},{"at":"2026-09-01T18:40:11+00:00","matchedBy":"fuzzy","similarity":86,"confidence":79,"ip":"198.51.100.7","origin":"https://shop.example.com","incognito":false,"bot":"","privacy":"","tag":"login"}]}}}},"401":{"description":"This endpoint needs a secret key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_key","message":"This endpoint needs a secret key.","requestId":"9f1c0a7b23d45e68"}}}}},"404":{"description":"No visitor with that id. Also the answer when the id belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unknown_visitor","message":"No visitor with that id. Also the answer when the id belongs to another account.","requestId":"9f1c0a7b23d45e68"}}}}}}},"delete":{"tags":["Fingerprinting"],"operationId":"deleteVisitor","summary":"Erase a visitor","description":"Deletes the visitor and every sighting of it. Immediate and irreversible.\n\nHere because erasure requests reach you, not us: for the browsers you identify you are the\ncontroller and we are your processor. This is how you action one.","security":[{"SecretKey":[]}],"parameters":[{"name":"visitorId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{32}$"},"example":"5f2a9c1e7b4d3086af51cc92e0b7d143"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"},"example":{"deleted":true,"visitorId":"5f2a9c1e7b4d3086af51cc92e0b7d143"}}}},"401":{"description":"This endpoint needs a secret key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_key","message":"This endpoint needs a secret key.","requestId":"9f1c0a7b23d45e68"}}}}},"404":{"description":"No visitor with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unknown_visitor","message":"No visitor with that id.","requestId":"9f1c0a7b23d45e68"}}}}}}}}},"components":{"securitySchemes":{"PublishableKey":{"type":"http","scheme":"bearer","description":"A `pk_live_...` key. Safe to publish in a page; restricted to the origins you register. The browser library sends it in the request body instead of a header, which keeps the call a CORS \"simple request\" and saves a preflight round trip on every page load. Both are accepted."},"SecretKey":{"type":"http","scheme":"bearer","description":"An `sk_live_...` key. Server side only. Never put one in a browser, a mobile app bundle, or anything else you hand to a user."}},"schemas":{"IdentifyRequest":{"type":"object","required":["components"],"properties":{"key":{"type":"string","description":"Your publishable key, when it is not in the Authorization header.","example":"pk_live_7QZ3M8XKD2VN0BHR5TJWY4FC61PSAE9G"},"components":{"type":"object","description":"Signal name to a 32-character hash of its value. The raw values never leave the browser: nothing here can reconstruct which fonts somebody has installed. Unknown names are discarded.","additionalProperties":{"type":"string","maxLength":64}},"unstable":{"type":"array","items":{"type":"string","enum":["platform","colorDepth","hardwareConcurrency","deviceMemory","engine","canvas","webgl","fonts","audio","fontPreferences","screen","speech","timezone","languages","cssFeatures","pixelRatio","touch","vendorFlavors","math","sensors","viewport","uaVersion"]},"description":"Signals the browser would not reproduce twice in a row, or blanked outright. Excluded from the comparison on both sides. Sending a randomised value instead of listing it here is worse than sending nothing: it pushes a returning visitor below the threshold and splits one person into a stranger every session."},"visitorId":{"type":"string","pattern":"^[a-f0-9]{32}$","description":"The id already in the browser's storage, if any. Believed unless the fingerprint contradicts it outright."},"incognito":{"type":"boolean","description":"Best effort. Every browser has deliberately broken the reliable detections."},"bot":{"type":"string","description":"Automation the page could see. Empty means nothing obvious, not that nothing is there.","example":"webdriver"},"privacy":{"type":"string","description":"An anti-fingerprinting browser, where one was recognised.","example":"brave"},"tag":{"type":"string","maxLength":190,"description":"Your own reference. Stored and echoed back, never interpreted."}}},"IdentifyResponse":{"type":"object","properties":{"visitorId":{"type":"string","pattern":"^[a-f0-9]{32}$","description":"Stable for this browser, and unique to your account. The same browser at another AbuseScore customer has a different id and there is no way to connect the two."},"confidence":{"type":"integer","minimum":1,"maximum":99,"description":"How much to believe this. Never 100: there is no measurement here that justifies certainty. Under about 50 means treat it as a hint."},"matchedBy":{"type":"string","enum":["new","storage","strict","fuzzy"],"description":"How the browser was recognised. `storage` is the strongest, `fuzzy` means the fingerprint had drifted and was matched on weight."},"similarity":{"type":"integer","minimum":0,"maximum":100},"returning":{"type":"boolean"},"firstSeen":{"type":"string","format":"date-time"},"lastSeen":{"type":"string","format":"date-time"},"sightings":{"type":"integer"},"incognito":{"type":"boolean"},"bot":{"type":"string"},"privacy":{"type":"string"},"ip":{"type":"string"},"ipVersion":{"type":["integer","null"],"enum":[4,6,null]},"signals":{"type":"object","properties":{"collected":{"type":"integer"},"weight":{"type":"integer","description":"How much the collected signals were worth."},"maximum":{"type":"integer","description":"What a fully open browser would be worth."},"unstable":{"type":"array","items":{"type":"string"}}}}}},"Visitor":{"type":"object","properties":{"visitorId":{"type":"string"},"confidence":{"type":"integer"},"firstSeen":{"type":"string","format":"date-time"},"lastSeen":{"type":"string","format":"date-time"},"sightings":{"type":"integer"},"incognito":{"type":"boolean"},"bot":{"type":"string"},"privacy":{"type":"string"},"history":{"type":"array","description":"Most recent first. An id claiming to be months old with one sighting is worth a second look.","items":{"$ref":"#/components/schemas/Sighting"}}}},"Sighting":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"matchedBy":{"type":"string","enum":["new","storage","strict","fuzzy"]},"similarity":{"type":"integer"},"confidence":{"type":"integer"},"ip":{"type":"string"},"origin":{"type":"string"},"incognito":{"type":"boolean"},"bot":{"type":"string"},"privacy":{"type":"string"},"tag":{"type":"string"}}},"Deleted":{"type":"object","properties":{"deleted":{"type":"boolean"},"visitorId":{"type":"string"}}},"Answer":{"type":"object","description":"Fields present depend on your plan. See GET /v1/me.","properties":{"ip":{"type":"string"},"version":{"type":"integer","enum":[4,6]},"score":{"type":"integer","minimum":0,"maximum":100,"description":"Higher is riskier. Built from the reason codes below, capped per category so three blocklists describing one botnet do not count three times."},"band":{"type":"string","enum":["clean","low","medium","high"],"description":"The score bucketed, for when you want a label rather than a number."},"is_bogon":{"type":"boolean","description":"An address that should never appear on the public internet."},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2."},"continent":{"type":["string","null"]},"flags":{"type":"array","items":{"type":"string","enum":["tor_exit","tor_relay","vpn","proxy","hosting","cloud","privacy_relay","abuse","abuse_l1","scanner","bogon","mobile","business","residential","education","government","anycast","geo_disagree"]},"description":"What this address is. Tor exit, Tor relay, VPN and privacy relay are four different things and never collapse into one."},"asn":{"type":["integer","null"]},"as_name":{"type":["string","null"]},"as_domain":{"type":["string","null"]},"network":{"type":["string","null"],"description":"What kind of network it is, where that is known."},"prefix":{"type":["string","null"],"description":"The range this answer covers. CIDR where it is exactly one prefix, start-end otherwise."},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"},"description":"Why the score is what it is, biggest mover first. This is the part worth building a rule on."},"categories":{"type":"object","description":"The arithmetic: each category's raw total, its cap, and what was actually applied."},"sources":{"type":"integer","description":"How many independent sources described this range."},"data_age_seconds":{"type":"integer","description":"How old the compiled data is."}}},"Reason":{"type":"object","properties":{"code":{"type":"string","description":"Stable. Branch on this."},"weight":{"type":"integer","description":"Points it contributed. Negative is positive reputation."},"category":{"type":"string","description":"Which cap it counts against. Empty means uncapped."},"detail":{"type":"string"}}},"BulkRequest":{"type":"object","required":["ips"],"properties":{"ips":{"type":"array","items":{"type":"string"},"description":"Up to your plan's bulk_max."}}},"BulkResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/Answer"}},"count":{"type":"integer"}}},"Account":{"type":"object","properties":{"plan":{"type":"object"},"usage":{"type":"object"},"credits":{"type":"integer","description":"Prepaid lookups left. Spent before the subscription allowance, because credits expire and an allowance does not carry over."},"data":{"type":["object","null"],"description":"How old the compiled data is, and how much of it there is."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable. Branch on this, not on the message."},"message":{"type":"string","description":"For a person. May change."},"requestId":{"type":"string","description":"Quote this when you contact support."}}}}}}}}