API reference/ip

Report IP outcomes

POST/api/v1/ip/feedback

authentication
Secret API key as a bearer token
billing
Free.

Tells Spaw what an address turned out to be after the fact: abuse (spam, scraping, credential stuffing), fraud (a confirmed fraudulent order or account), bot (automated traffic that got through), or chargeback. Your own reports feed your own later lookups of that address: any report inside 30 days scores reported_abuse (+60) with the dataset abuse-feedback. Once three or more accounts report the same address inside 30 days, every account's lookups of it score reported_abuse_widely (+40); only the number of reporting accounts is ever read, never who reported or what. Neither signal turns a null score into a number: an address no feed evaluated stays unscored.

Addresses are canonicalised like a lookup and stored only as a salted SHA-256 hash with the outcome and a timestamp, never in clear; a hash can confirm a later lookup of the same address but cannot be read back into one. Items whose address is malformed or in a reserved range are skipped and counted, not rejected. Feedback is free, never billed and never logged as a lookup. Reports are kept for 90 days.

Request body

field type description
itemsrequiredobject[]1 to 1,000 outcomes per request.
items[].iprequiredstringAn IPv4 or IPv6 address, in any common notation; IPv4-mapped IPv6 collapses to dotted-quad. At most 64 characters.
items[].outcomerequiredstringOne of: abuse, fraud, bot, chargeback.
items[].occurred_atstring | nullWhen it happened. Defaults to now.

Example request

curl -X POST https://spaw.co/api/v1/ip/feedback \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "ip": "203.0.113.9",
      "outcome": "fraud"
    },
    {
      "ip": "2001:db8::1",
      "outcome": "bot",
      "occurred_at": "2026-09-05T09:00:00Z"
    }
  ]
}'
const response = await fetch('https://spaw.co/api/v1/ip/feedback', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_…',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "items": [
      {
        "ip": "203.0.113.9",
        "outcome": "fraud"
      },
      {
        "ip": "2001:db8::1",
        "outcome": "bot",
        "occurred_at": "2026-09-05T09:00:00Z"
      }
    ]
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/ip/feedback',
    headers={'Authorization': 'Bearer sk_live_…'},
    json={
        'items': [
            {
                'ip': '203.0.113.9',
                'outcome': 'fraud'
            },
            {
                'ip': '2001:db8::1',
                'outcome': 'bot',
                'occurred_at': '2026-09-05T09:00:00Z'
            }
        ]
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/ip/feedback');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_live_…', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'items' => [
            [
                'ip' => '203.0.113.9',
                'outcome' => 'fraud'
            ],
            [
                'ip' => '2001:db8::1',
                'outcome' => 'bot',
                'occurred_at' => '2026-09-05T09:00:00Z'
            ]
        ]
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

202The outcomes were recorded.

{
    "success": true,
    "data": {
        "recorded": 2,
        "skipped": 0
    }
}

401The key is missing, malformed, or revoked.

{
    "success": false,
    "error": {
        "code": "UNAUTHENTICATED",
        "message": "Provide a valid API key as a bearer token.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

422The request body could not be validated; `error.errors` lists the fields.

{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

429Over 5 requests per second for the key. Retry after the limit resets.

{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json