Test Ad

CashLink Developer API

NSFW Image Scan API

Asynchronous image safety classification with one-time BCH entitlements or an administrator-issued privileged token.

API base URL
https://test-linking.cc/api/v2/nsfw-scans
Current price1,000 sat
Maximum file100 MiB
Service statusAvailable
On this page

NSFW Image Scan API

Scan image content asynchronously. A public client can purchase one scan entitlement with BCH, while a permanent privileged token issued by a super administrator can submit scans without creating a payment order.

Quick start

Permanent privileged token

One privileged token works with both the NSFW and ClamAV APIs. It has no expiry, charge, usage quota, or API rate limit, although a super administrator can revoke it. File validation, the 100 MiB maximum, Idempotency-Key, scanner readiness, and other safety checks still apply.

CashLink nodes may use a privileged token with GET https://test-linking.cc/api/v2/security-scans/provider to discover local capacity. Delegated submissions carry X-CashLink-Scan-Delegation-From; a provider rejects its own instance, a second delegation, or a non-local effective backend, limiting delegation to one hop. The provider returns probability, frame, and model data; the caller keeps its own NSFW threshold.

The plaintext token is shown only once when an administrator creates it. Store it like a password and never put it in a URL, client-side source, or logs.

curl --request POST 'https://test-linking.cc/api/v2/nsfw-scans' \
  --header 'Authorization: Bearer YOUR_PRIVILEGED_TOKEN' \
  --header 'Idempotency-Key: nsfw-2026-0001' \
  --form 'file=@./photo.jpg'

A new scan returns 202 Accepted. Poll the returned scan ID with the same token:

curl --header 'Authorization: Bearer YOUR_PRIVILEGED_TOKEN' \
  'https://test-linking.cc/api/v2/nsfw-scans/019c0000-0000-7000-8000-000000000001'

One-time BCH payment

  1. Read the current quote.
  2. Create an anonymous payment order and immediately store the one-time order_token.
  3. After saving the token, activate the order and pay the exact BCH amount from its bip21 value or QR image.
  4. Poll the order until entitlement_status is available.
  5. Submit one image with the same order_token; the entitlement atomically becomes consumed.
curl 'https://test-linking.cc/api/v2/nsfw-scans/quote'

curl --request POST 'https://test-linking.cc/api/v2/nsfw-scans/payment-orders' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: nsfw-order-2026-0001' \
  --data '{"refund_address":"bitcoincash:YOUR_MAINNET_CASHADDR"}'

A new order returns 201 Created and the one-time order_token. Replaying the same request returns 200 OK without revealing the token again. Activate the order within 15 minutes. Its price is fixed when the order is created, so later administrator price changes do not alter it.

curl --request POST 'https://test-linking.cc/api/v2/nsfw-scans/payment-orders/ORDER_UUID/activate' \
  --header 'Authorization: Bearer YOUR_ORDER_TOKEN'

curl --header 'Authorization: Bearer YOUR_ORDER_TOKEN' \
  'https://test-linking.cc/api/v2/nsfw-scans/payment-orders/ORDER_UUID'

An entitlement becomes available when a sufficient zero-confirmation payment is detected. A later chain reversal revokes an unused entitlement; if a scan was already submitted, the system records a financial exception for administrator review.

Endpoints

Method Path Authorization Purpose
GET /quote None Current per-scan price and limit
POST /payment-orders None Create an anonymous BCH order
POST /payment-orders/{order}/activate Order token Activate an order after saving its token
GET /payment-orders/{order} Order token Read payment and entitlement state
GET /payment-orders/{order}/events?after=0 Order token Read payment events after a revision
GET /payment-orders/{order}/qr Order token Fetch the BCH payment QR as SVG
GET /payment-orders/{order}/refunds Order token Read refund state
POST / Order or privileged token Submit one image
GET /{scan} Token used for submission Poll scan state and result
GET /api/v2/security-scans/provider Privileged token Discover non-secret local scanner capacity for one-hop delegation

Successful JSON responses use a data envelope. Payment-order creation also has a top-level order_token. JSON and QR responses are private and must not be cached.

Upload rules

  • Send multipart/form-data with one field named file.
  • Remote url input is prohibited; the service scans only uploaded bytes.
  • The file must be non-empty and no larger than 100 MiB.
  • The server-detected content must be JPEG, PNG, WebP, or GIF. A filename extension does not replace content validation.
  • Idempotency-Key is required and may contain at most 255 characters.

Scan states and result

The normal states are queued and processing. A transient dependency failure may produce deferred; terminal states are completed and failed. After 24 hours the full result becomes expired, and reads return 410 Gone.

{
  "data": {
    "id": "019c0000-0000-7000-8000-000000000001",
    "scan_type": "nsfw",
    "status": "completed",
    "attempt_count": 1,
    "result": {
      "verdict": "safe",
      "probability": 0.031,
      "threshold": 0.85,
      "sampled_frames": 1,
      "highest_risk_frame": 0,
      "model_version": "model-version",
      "duration_ms": 84
    },
    "error": null,
    "created_at": "2026-09-11T12:00:00+08:00",
    "completed_at": "2026-09-11T12:00:01+08:00",
    "expires_at": "2026-09-12T12:00:01+08:00"
  }
}

verdict is safe or nsfw. Use the returned threshold when interpreting the probability. Model version, sampled-frame count, and the highest-risk frame are returned for auditability.

Idempotency and retries

Use a stable Idempotency-Key for both order creation and scan submission. Repeating the same file with the same credential, scan type, and key returns the original scan without consuming another entitlement. Reusing the key for different content returns 409 Conflict.

A 503 response means no scan was created and no paid entitlement was consumed. Retry later with the same key. A final technical scan failure deletes the source and restores the same paid entitlement to available.

Errors and security

Status Meaning
401 Bearer token is missing, invalid, or revoked
404 Resource is absent, the token does not own it, or the service is disabled
409 Idempotency conflict, invalid order state, or unavailable entitlement
410 The 24-hour result retention period has elapsed
422 Invalid field, CashAddr, file size, or detected image format
503 Scanner unavailable; no scan or entitlement consumption occurred

Source bytes exist only in private quarantine. After the result checkpoint, the source is deleted before a terminal state is exposed. The API never returns the filename, filesystem path, raw scanner output, or infrastructure details.

Download the complete machine-readable OpenAPI 3.1 contract.

This is a Legacy BCH address

Legacy addresses can make the network and purpose harder to verify. We recommend converting it to CashAddr with the on-site tool. If you have verified the recipient, you may allow this submission once.

Open BCH address converter