API reference · Starter, Pro, Enterprise

    Company API v1

    Integrate reports, monitoring and response status into your security workflow. This reference covers the implemented v1 resource routes. All data examples are illustrative, not live records; angle-bracket placeholders must be replaced with returned values. Domains and URLs shown in responses are defanged for publication.

    Authentication and base URL

    Base URL: https://ugapgzpffrykyglevlhz.supabase.co/functions/v1/blackwall-api

    Create a company API key in your company dashboard. Keys start with fp_co_; send the full secret in the X-API-Key header. Keep it in server-side secret storage, never in browser bundles or logs. Revoke and replace exposed keys. An expired or revoked key cannot authenticate.

    Available scopes are read, submit, watchlist, takedowns and findings. Scopes do not override plan capabilities. All POST routes also require the plan’s API submission feature, including watchlist creation.

    For examples, set BASE to the base URL and FP_KEY to your key. Replace UUID placeholders in paths. Replace $TARGET_DOMAIN and $OWNED_DOMAIN in request bodies with actual domains before running: these are literal placeholders, not automatically expanded inside JSON. Optional screenshot and evidence arrays may be omitted. Successful responses are JSON; POST creation normally returns 201, other operations return 200.

    Idempotency with external_ref

    POST /v1/reports accepts an optional case-sensitive external_ref of 1–128 printable non-space ASCII characters. Reuse it when retrying the same submission. A duplicate for the same company returns the existing id, report_ref and status with 200 rather than creating another report; it does not update the original payload. This mechanism is specific to report submission, not a general idempotency header.

    See the category list.

    Case status

    Every report you submit is a case and is accepted immediately; there is no triage queue. A case moves forward through four steps and never goes back:

    1. accepted (Accepted): the case is in our queue.
    2. in_progress (In progress): we are working on it and have contacted the responsible providers.
    3. resolved_taken_down (Resolved – taken down): the site is offline. We confirm this with repeated checks.
    4. resolved_no_threat (Resolved – no threat): no action was needed. resolution_note explains why.

    Use case_status for logic and case_status_label for display. Each step triggers a report.status_changed webhook and an email to the submitter.

    Report categories

    POST /v1/reports requires one category:

    • phishing-page · Fake login / phishing page
    • scam-shop · Fake shop or non-delivery scam
    • crypto-scam · Crypto / investment scam
    • advance-fee · Advance-fee or suspicious payment request
    • impersonation · Impersonates a person, brand or authority
    • malware-download · Malware or unwanted download
    • urgency-tactics · Fear / urgency manipulation tactics
    • doxxing · Publishes personal data of others
    • leaked-data · Hosts leaked or stolen personal data
    • harassment · Harassment, hate speech or bullying
    • threats · Threats of violence or intimidation
    • defamation · Defamation or false claims about a person
    • stolen-content · Stolen or copyrighted content
    • illegal-content · Illegal content

    Endpoint reference

    GET /v1/status

    Required scope: read

    Inspect your key prefix, scopes, current plan, usage today and limits. Read the daily and burst limits from the response rather than hardcoding them.

    curl -X GET "$BASE/v1/status" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "key": {
        "prefix": "fp_co_…",
        "scopes": [
          "read"
        ]
      },
      "plan": "<current-plan>",
      "usage_today": "<request-count>",
      "limits": {
        "daily": "<plan-allowance>",
        "burst_per_minute": "<current-burst-limit>"
      }
    }

    GET /v1/reports?limit=50&case_status=in_progress

    Required scope: read

    List reports, newest first. Optional case_status filters by case step: accepted, in_progress, resolved_taken_down or resolved_no_threat. Collection limit defaults to 50 and is clamped to 1–200. These collections do not return pagination cursors.

    curl -X GET "$BASE/v1/reports?limit=50&case_status=in_progress" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "reports": [
        {
          "id": "<report-id>",
          "report_ref": "<report-reference>",
          "case_status": "accepted",
          "case_status_label": "Accepted",
          "resolution_note": null,
          "domain": "suspect[.]example",
          "created_at": "<ISO timestamp>",
          "updated_at": "<ISO timestamp>"
        }
      ]
    }

    GET /v1/reports/<report-id>

    Required scope: read

    Read one report using its returned id (UUID), not report_ref. Includes the case history, takedowns and evidence. Screenshot URLs are temporary signed URLs and can be null; do not store them as permanent evidence links.

    curl -X GET "$BASE/v1/reports/<report-id>" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "id": "<report-id>",
      "report_ref": "<report-reference>",
      "case_status": "accepted",
      "case_status_label": "Accepted",
      "resolution_note": null,
      "domain": "suspect[.]example",
      "created_at": "<ISO timestamp>",
      "updated_at": "<ISO timestamp>",
      "history": [
        {
          "case_status": "accepted",
          "label": "Accepted",
          "at": "<ISO timestamp>"
        },
        {
          "case_status": "in_progress",
          "label": "In progress",
          "at": "<ISO timestamp>"
        }
      ],
      "takedowns": [
        {
          "id": "<takedown-id>",
          "status": "<status>",
          "created_at": "<ISO timestamp>",
          "resolved_at": null
        }
      ],
      "evidence": [
        {
          "sha256": "<SHA-256 hash>",
          "captured_at": "<ISO timestamp>",
          "screenshot_url": null
        }
      ]
    }

    POST /v1/reports

    Required scope: submit

    Submit a report. Supply url or domain (url takes precedence), a supported category and a description of 30–20,000 characters. Optional evidence_urls accepts up to 20 HTTP(S) URLs. Optional screenshots accepts up to three base64 PNG/JPEG images, each at most 1.5 MB; matching data-URL prefixes are accepted. The plan must enable API submission.

    curl -X POST "$BASE/v1/reports" \
      -H "X-API-Key: $FP_KEY" \
      -H "Content-Type: application/json" \
      --data '{"domain":"$TARGET_DOMAIN","category":"phishing-page","description":"A fake login page is impersonating our brand and collecting account credentials.","external_ref":"incident-demo-1","evidence_urls":[],"screenshots":[]}'

    Example response · 201

    {
      "id": "<report-id>",
      "report_ref": "<report-reference>",
      "case_status": "accepted",
      "case_status_label": "Accepted",
      "resolution_note": null
    }

    GET /v1/watchlist?limit=50

    Required scope: watchlist

    Read monitored watchlist domains. Supports the collection limit parameter.

    curl -X GET "$BASE/v1/watchlist?limit=50" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "watchlist": [
        {
          "id": "<watchlist-id>",
          "domain": "brand[.]example",
          "created_at": "<ISO timestamp>",
          "last_scanned_at": null,
          "scan_status": "<status>"
        }
      ]
    }

    POST /v1/watchlist

    Required scope: watchlist

    Add a domain. Requires the watchlist scope and a plan with API submission enabled. A reached watchlist cap returns 409 watchlist_limit.

    curl -X POST "$BASE/v1/watchlist" \
      -H "X-API-Key: $FP_KEY" \
      -H "Content-Type: application/json" \
      --data '{"domain":"$OWNED_DOMAIN"}'

    Example response · 201

    {
      "id": "<watchlist-id>",
      "domain": "brand[.]example",
      "created_at": "<ISO timestamp>"
    }

    DELETE /v1/watchlist?id=<watchlist-id>

    Required scope: watchlist

    Delete an owned watchlist entry by its id query parameter. Missing id returns 400 invalid_input.

    curl -X DELETE "$BASE/v1/watchlist?id=<watchlist-id>" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "deleted": true
    }

    GET /v1/findings?limit=50&min_score=50

    Required scope: findings

    Read Brand Shield findings, newest first. Optional since is an ISO timestamp filtering first_seen inclusively; min_score is a number from 0 to 100; status is an exact filter. Supports limit.

    curl -X GET "$BASE/v1/findings?limit=50&min_score=50" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "findings": [
        {
          "id": "<finding-id>",
          "monitored_domain": "brand[.]example",
          "candidate_domain": "suspect[.]example",
          "url": "hxxps://suspect[.]example/",
          "score": "<score>",
          "risk": "<risk>",
          "reasons": [],
          "signals": {},
          "status": "<status>",
          "first_seen": "<ISO timestamp>",
          "last_seen": "<ISO timestamp>"
        }
      ]
    }

    POST /v1/findings/<finding-id>/report

    Required scope: submit

    Create a report from an owned finding. Uses the submit scope, not the findings scope. No request body is required; the plan must enable API submission.

    curl -X POST "$BASE/v1/findings/<finding-id>/report" \
      -H "X-API-Key: $FP_KEY"

    Example response · 201

    {
      "id": "<report-id>",
      "report_ref": "<report-reference>",
      "case_status": "accepted",
      "case_status_label": "Accepted",
      "resolution_note": null
    }

    GET /v1/takedowns?limit=50

    Required scope: takedowns

    List takedown requests, newest first. Supports limit. There is no Company API v1 endpoint to create a takedown request.

    curl -X GET "$BASE/v1/takedowns?limit=50" \
      -H "X-API-Key: $FP_KEY"

    Example response · 200

    {
      "takedowns": [
        {
          "id": "<takedown-id>",
          "report_id": "<report-reference>",
          "domain": "suspect[.]example",
          "status": "<status>",
          "created_at": "<ISO timestamp>",
          "resolved_at": null
        }
      ]
    }

    Plan limits

    Company API and Threat API allowances are counted separately per company and reset daily at 00:00 UTC. GET /v1/status returns your current limits and usage. Starter keys can read; submitting cases and managing the watchlist need Pro or Enterprise.

    CapabilityNo keyFree keyStarterProEnterprise
    PriceFreeFree account$99/mo$269/moCustom
    Threat API lookups20 / hour per IP1,000 / day · 60 / min– / day– / day– / day
    Threat feeds & change sync24 feed downloads / day
    Company API: read cases, findings, takedowns– requests / day– requests / day– requests / day
    Submit reports & manage watchlist via API–––
    Brand Shield findings–––
    Webhooks for case updates–––
    SIEM export (CSV, CEF, JSON)–––
    Key typenonefp_live_…fp_co_…fp_co_…fp_co_…

    Company plans add a burst limit of 60 requests per minute per key. Not sure yet? Request a 14-day demo with Pro features, or compare plans on the pricing page.

    Errors and retries

    Errors use {error, message}. Handle the HTTP status and machine-readable error code rather than matching message text. Resource validation can return 400 invalid_input, missing scopes return 403 insufficient_scope, unavailable capabilities return 403 forbidden, missing resources return 404 not_found and watchlist caps return 409 watchlist_limit. Authentication failures return 401, quotas return 429 rate_limit and unexpected failures return 500. Correct invalid input before retrying; use backoff for quota or temporary failures and external_ref for report retries.

    {
      "error": "insufficient_scope",
      "message": "Required scope: submit"
    }

    Webhooks

    Case updates, completed takedowns and new Brand Shield findings can be pushed to your endpoint. See the webhooks guide for setup, events and payloads.

    Changelog

    v1 — Company report submission and status, watchlist management, Brand Shield findings, takedown status and company webhook event documentation.