Skip to main content
POST
Review an organization proposal

Overview

Record your organization’s decision on a proposal that is waiting on you — one ChainPatrol escalated to you, or one on a report you submitted yourself. These are exactly the proposals your Review page shows and the ones GET /organization/reports?needsCustomerReview=true counts, so this endpoint lets you clear that queue without opening the web app. There is one endpoint for all three outcomes, discriminated by decision: WATCHLIST is a flavor of rejection rather than a separate outcome: it is stored as a REJECT review with the asset watchlisted, which is the same write the Review page performs. That is why a successful watchlist response comes back with review.decision: "REJECT" and watchlisted: true.
A review cannot be undone through the API. APPROVE blocks the asset, and because assets are unique on type and content, that status is global rather than scoped to your organization. Read the proposal before you act on it, and use the rate limit as a backstop rather than a target.

Quick Start

Authentication

Include your API key in the X-API-KEY header:

Finding a proposal to review

Both IDs this endpoint needs come from the reports listing. Ask for the proposals that are waiting on you, then read proposals[].id and proposals[].asset.id:
cURL

Example Request

Request Body

proposalId is a path parameter, so it is not repeated in the body.

Why assetId is required

assetId is an echo-back, not a lookup. The server compares it against the proposal’s current asset and returns 409 on a mismatch, which means a stale or mistyped proposalId fails loudly instead of reviewing a different asset than the one you read. Take both values from the same listing response and pass them together.

Block labels

label is required when approving and ignored otherwise. Brand Impersonation and Employee Impersonation also require brandId.

Reject reasons

Reject reasons feed ChainPatrol’s detection-tuning breakdown, so pick the closest one rather than putting the real reason in note:

Response

Success Response

Watchlist Response

A watchlist request is recorded as a rejection with the asset parked:

Response Fields

Safety checks

Approving over the API is held to the same checks as the Review page, and unlike the UI they cannot be overridden:
  • Legitimacy rules. If any enabled legitimacy rule concluded the asset looks legitimate — a Tranco-listed domain, a regex match on a domain you own — approval returns 403. A person has to make that call on the Review page.
  • Allowlisted assets. An asset that is currently ALLOWED cannot be blocked here, also 403.
Both are deliberate dead ends rather than warnings. When you hit one, review the proposal in the app.

Rate limit

Reviews are limited to 10 per organization per rolling 24 hours. The budget is shared across every API credential your organization has, and reviews made from the Review page do not count against it. Exceeding it returns 429. The limit exists because a review is not undoable and looping over a queue is easy to do by accident. Treat it as a backstop: if you are hitting it regularly, the queue is better cleared in the app.

Error Responses

400 Bad Request

A conditional requirement was not met, or the asset type cannot be watchlisted:

403 Forbidden

The credential is not a customer member of the organization, or a safety check refused the write:

404 Not Found

The proposal does not exist, or is not one of yours:

409 Conflict

assetId does not match the proposal’s current asset, or the proposal has already been resolved:

429 Too Many Requests

The organization’s daily review budget is spent:

Use Cases

Clear the queue that is waiting on you

Read the proposals waiting on your organization, then act on each one:

Park an asset that went quiet

An asset that is dead or parked is not a false positive — watchlist it so ChainPatrol re-evaluates it if it comes back:
cURL

From the CLI

The ChainPatrol CLI wraps this endpoint, and confirms before it writes:
Pass --yes to skip the confirmation in a script, or --dry-run to print the payload without submitting it.

Best Practices

Read before you write

Every request needs assetId from a listing you just read, which is deliberate. Fetch the proposal, decide from its asset and scan data, then review it — do not synthesize IDs or replay an old listing.

Treat 403 as routing, not failure

A 403 from a safety check means the decision needs a person, not that your integration is broken. Log the proposal ID and move on; do not retry.

Do not retry a 409

A 409 means either the asset does not match or the proposal is already resolved. Re-read the proposal instead of retrying with the same body.

Notes

  • Reviews must be attributed to a customer-role member of the organization. Staff credentials are refused with 403.
  • Asset status is global: assets are unique on type and content, so approving blocks the asset everywhere rather than only for your organization.
  • Not every asset type can be watchlisted. Types the watchlist cannot re-check are refused with 400 instead of silently skipping the park.
  • GET /organization/reports returns proposals[].id specifically so proposals can be addressed here.

Authorizations

X-API-KEY
string
header
required

Your API key. This is required by most endpoints to access our API programatically. Reach out to us at support@chainpatrol.io to get an API key for your use.

Path Parameters

proposalId
integer
required

ID of the proposal to review. Read it from proposals[].id on GET /organization/reports.

Required range: x > 0

Body

application/json

Review a proposal on your organization's behalf

Records your organization's decision on a proposal ChainPatrol escalated to you, or on a proposal belonging to a report you submitted yourself. These are exactly the proposals your Review page shows, and the ones counted by GET /organization/reports?needsCustomerReview=true.

assetId
integer
required

ID of the asset this proposal is for, from proposals[].asset.id on GET /organization/reports. Required, and must match the proposal's current asset — a mismatch returns 409. This proves the request was built from a proposal you actually read, rather than from a guessed or stale ID.

Required range: x > 0
decision
enum<string>
required

What to do with the proposal. APPROVE blocks the asset, REJECT declines it, and WATCHLIST declines it but parks the asset so it is re-evaluated if it comes back alive. WATCHLIST is recorded as a rejection with the asset watchlisted — the same write the Review page performs.

Available options:
APPROVE,
REJECT,
WATCHLIST
slug
string

Organization slug. Optional for organization-scoped API keys, which resolve the organization from the key itself. Required when your credentials can reach more than one organization.

label
enum<string>

Why the asset is being blocked. Required when decision is APPROVE, ignored otherwise. One of: Brand Impersonation, Employee Impersonation, Fake Employee, Targeting Org Users, General Phishing, C2 Server, False Positive, Organization Member Impersonation, Targeting Organization.

  • Brand Impersonation — The asset is directly using the Brand's name, logo, or other trademarks
  • Employee Impersonation — The asset is impersonating a specific employee of the organization
  • Fake Employee — The asset claims to work at the company but is not directly impersonating a specific person
  • Targeting Org Users — A scam the org wants taken down that falls outside direct impersonation
  • General Phishing — This asset is trying to steal user funds
  • C2 Server — Command and Control server providing backend infrastructure for phishing operations
  • False Positive — This asset was incorrectly flagged and should be allowed
  • Organization Member Impersonation — The asset is impersonating a member or employee of the organization
  • Targeting Organization — A scam the org wants taken down that falls outside direct impersonation
Available options:
Brand Impersonation,
Employee Impersonation,
Fake Employee,
Targeting Org Users,
General Phishing,
C2 Server,
False Positive,
Organization Member Impersonation,
Targeting Organization
brandId
integer

Brand the asset impersonates. Optional for most labels, but required for Brand Impersonation and Employee Impersonation, which name a specific impersonation target. List your brands with GET /organization/brands.

Required range: x > 0
rejectReason
enum<string>

Why the proposal is being rejected. Required when decision is REJECT, ignored otherwise. These are the same reasons the Review page offers, and they feed ChainPatrol's detection-tuning breakdown — pick the closest one rather than relying on note.

Available options:
irrelevant,
no_malicious_activity,
insufficient_evidence,
not_targeting_org,
decayed
watchlistReason
enum<string>

Why the asset is being parked. Required when decision is WATCHLIST, ignored otherwise. Use OTHER for anything not covered, in which case note becomes required and is treated as the reason. Note that the watchlist only covers asset types that can be re-checked for revival (URL, PAGE, EMAIL, and the social platforms); watchlisting any other type is rejected, so reject the proposal instead.

Available options:
DEAD,
PARKING,
NOT_ENOUGH_EVIDENCE,
OTHER
note
string

Free-text detail recorded on the review, alongside the structured reason. Required when watchlistReason is OTHER.

Maximum string length: 1000

Response

Successful response

Successful operation

proposal
object
required

The reviewed proposal

review
object
required

The review that was created

watchlisted
boolean
required

Whether the asset was added to the watchlist as part of this review