Back to homepage

API Documentation

Integrate Made By Humans AI detection directly into your publishing workflow. This REST API lets you verify images and text programmatically — perfect for building CMS plugins, editorial dashboards, or automated content pipelines.

REST API

JSON request/response over HTTPS

API Key + Bearer Token

Secure key-based auth for plugins & integrations

Token Billing

Fixed per-operation token costs

Base URL: https://made.by.humans.press — All endpoints are relative to this base. Use HTTPS in production.

Getting Started

Follow these steps to start using the Made By Humans API from a WordPress plugin or any external integration.

1

Create an account

Sign up with your name, email, and publication. You can also use the web tool immediately after signing up.

2

Ensure you have tokens

You need a positive token balance to generate API credentials. Check your balance on the Account page.

3

Generate API credentials

Go to the API Credentials section on your Account page. Click Generate new API key. You will receive:

  • API User ID — a unique identifier like MBH-A3K7NW2P (shown permanently)
  • API Key — a secret key like mbh_a1b2c3d4... (shown once at creation)
Important: Copy your API key immediately. It cannot be displayed again. If lost, revoke the key and generate a new one.
4

Exchange credentials for a bearer token

Send your API User ID and API Key to the token endpoint to receive a short-lived bearer token.

Request
POST /api/v1/auth/token
Content-Type: application/json

{
  "apiUserId": "MBH-A3K7NW2P",
  "apiKey": "mbh_your_api_key_here"
}
Response (200)
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}
5

Make API requests

Include the bearer token in the Authorization header of every API request.

Example
curl -X POST https://made.by.humans.press/api/text/pangram \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Article text to analyse..."}'

Bearer tokens expire after 1 hour. Request a new token when you receive a 401 response.

Authentication

The API supports two authentication methods. Use API key authentication for external integrations like WordPress plugins, and session authentication for browser-based access.

Method 1: API Key + Bearer Token (recommended for plugins)

This is the recommended method for WordPress plugins, CMS integrations, and any server-side client. Your API key never needs to be sent on every request — it is exchanged once for a short-lived bearer token.

  1. Generate an API User ID and API Key from the Account page.
  2. POST to /api/v1/auth/token with your apiUserId and apiKey to receive a bearer token.
  3. Include Authorization: Bearer <token> on all subsequent API requests.
  4. When the token expires (1 hour), request a new one using the same credentials.
Token endpointPOST /api/v1/auth/token
Token formatHS256 JWT
Token lifetime1 hour
Header formatAuthorization: Bearer <token>
Rate limit30 requests per 15 minutes per IP
Example: Full API key auth flow (cURL)
# 1. Exchange credentials for a bearer token
curl -X POST https://made.by.humans.press/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"apiUserId": "MBH-A3K7NW2P", "apiKey": "mbh_your_api_key"}'
# → { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }

# 2. Use the bearer token for API requests
curl -X POST https://made.by.humans.press/api/text/pangram \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Article text to analyse..."}'

Method 2: Session Cookie (browser / web app)

The web application uses session-based authentication with HTTP-only cookies. This method is used automatically when you access the tool through the browser.

  1. POST to /api/auth/login with your email and password.
  2. The server responds with a Set-Cookie header containing an editorial_ai_session JWT.
  3. Include this cookie in all subsequent API requests.
Cookie nameeditorial_ai_session
AlgorithmHS256 (JWT)
Lifetime7 days
HttpOnlyYes
SecureYes (production)
SameSiteLax
Example: Login & use session (cURL)
# 1. Authenticate and save the session cookie
curl -X POST https://made.by.humans.press/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}' \
  -c cookies.txt

# 2. Use the session cookie in subsequent requests
curl https://made.by.humans.press/api/submissions/history \
  -b cookies.txt

API Key Management

Manage API keys programmatically. These endpoints require session authentication (you must be logged in). You can also manage keys from the Account page.

POST/api/v1/auth/token

Exchange your API User ID and API Key for a short-lived bearer token. This is the primary authentication endpoint for external integrations. No session required.

Request body

apiUserIdstringrequiredYour API User ID (e.g. MBH-A3K7NW2P)
apiKeystringrequiredYour API key (e.g. mbh_a1b2c3d4...)
Request
POST /api/v1/auth/token
Content-Type: application/json

{
  "apiUserId": "MBH-A3K7NW2P",
  "apiKey": "mbh_your_api_key_here"
}
Response (200)
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}
Response (401)
{
  "error": "Invalid credentials."
}

Notes

  • Bearer tokens expire after 1 hour
  • Rate limited to 30 requests per 15 minutes per IP
  • Revoked API keys will return 401
GET/api/api-keys

List all API keys for the authenticated user. Also returns the API User ID and usage statistics. Requires session auth.

Response (200)
{
  "ok": true,
  "apiUserId": "MBH-A3K7NW2P",
  "keys": [
    {
      "id": "cm7x...",
      "keyPrefix": "mbh_a1b2c3d4",
      "label": "WordPress Production",
      "revokedAt": null,
      "lastUsedAt": "2026-04-16T14:30:00.000Z",
      "createdAt": "2026-04-10T09:00:00.000Z"
    }
  ],
  "usage": {
    "totalCalls": 1247,
    "last30Days": 312,
    "byKey": [
      { "apiKeyId": "cm7x...", "_count": { "id": 312 } }
    ]
  }
}
POST/api/api-keys

Generate a new API key. Returns the full raw key once only. Requires a positive token balance. Requires session auth.

labelstringOptional label for identification (e.g. "WordPress Production")
Request
POST /api/api-keys
Content-Type: application/json

{
  "label": "WordPress Production"
}
Response (200)
{
  "ok": true,
  "apiUserId": "MBH-A3K7NW2P",
  "key": {
    "id": "cm7x...",
    "rawKey": "mbh_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8",
    "keyPrefix": "mbh_a1b2c3d4",
    "label": "WordPress Production",
    "createdAt": "2026-04-17T10:00:00.000Z"
  }
}
Important: The rawKeyis only returned at creation time. Store it securely — it cannot be retrieved again.
PATCH/api/api-keys/[id]

Update the label on an existing API key. Requires session auth.

labelstringrequiredNew label for the key
Request
PATCH /api/api-keys/cm7x...
Content-Type: application/json

{
  "label": "WordPress Staging"
}
DELETE/api/api-keys/[id]

Revoke an API key. The key will immediately stop working and any bearer tokens issued from it will be invalidated. Requires session auth.

Request
DELETE /api/api-keys/cm7x...
Response (200)
{
  "ok": true
}

Token System

Every analysis operation costs a fixed number of tokens. Tokens are deducted before the analysis begins. If your balance is insufficient, the API returns a 402 error and no tokens are spent.

Loading prices…

Token costs are configurable and may change. Use the GET /api/token-costs endpoint to fetch the current costs programmatically. Token balance is visible on the account page. Contact us to top up your balance.

Token Endpoints

Use these endpoints to read balance, transaction history, package options, and auto top-up settings. These endpoints are authenticated and use the same session cookie model.

GET/api/token-costs

Returns the current token cost for each analysis operation. This endpoint is public— no authentication is required. Use it to display accurate pricing to users or to calculate estimated costs before submitting analyses.

Response (200)
{
  "ok": true,
  "costs": {
    "text_analysis": 2,
    "image_detection": 2,
    "image_heatmap": 5,
    "web_detection": 2
  }
}

Response fields

  • text_analysis — tokens per text analysis (per 1,000 words)
  • image_detection — tokens per image AI detection
  • image_heatmap — tokens per manipulation heatmap
  • web_detection — tokens per reverse image search

Costs are configurable by admins and cached server-side for up to one minute.

GET/api/tokens/balance

Get the current effective token balance and forecast data.

Response (200)
{
  "ok": true,
  "balance": 520,
  "source": "publisher",
  "publisherId": 3,
  "publisherName": "Example News",
  "publisherRole": "ADMIN",
  "forecast": {
    "daysRemaining": 41,
    "dailyAverage": 12.4
  },
  "dailyUsage": [
    { "date": "2026-04-06", "tokens": 18 }
  ],
  "autoTopUp": {
    "enabled": true,
    "threshold": 10,
    "topUpAmount": 100
  }
}
GET/api/tokens/transactions?page=1&limit=25

List token transactions for the current user or active publisher.

Response (200)
{
  "ok": true,
  "transactions": [
    {
      "id": "cm7...",
      "amount": -2,
      "type": "DEBIT",
      "operation": "IMAGE_DETECTION",
      "description": "Image AI detection",
      "balanceBefore": 522,
      "balanceAfter": 520,
      "createdAt": "2026-04-06T12:00:00.000Z",
      "user": {
        "username": "reporter",
        "email": "reporter@newsroom.com"
      }
    }
  ],
  "page": 1,
  "totalPages": 2,
  "totalCount": 37
}
GET/api/tokens/purchase

List available token packages.

Response (200)
{
  "ok": true,
  "packages": [
    { "id": "starter", "tokens": 20, "priceCents": 200, "label": "€2 — 20 tokens" },
    { "id": "standard", "tokens": 500, "priceCents": 2000, "label": "€20 — 500 tokens" },
    { "id": "bulk", "tokens": 3500, "priceCents": 10000, "label": "€100 — 3,500 tokens" }
  ]
}
POST/api/tokens/purchase

Add a package of tokens. Payment provider wiring is not enabled yet, so this grants tokens directly in the current phase.

packageIdstringrequiredOne of starter, standard, bulk
Request
POST /api/tokens/purchase
Content-Type: application/json

{
  "packageId": "standard"
}
Response (200)
{
  "ok": true,
  "note": "Payment processing not yet integrated. Tokens added for testing.",
  "package": "€20 — 500 tokens",
  "tokensAdded": 500,
  "newBalance": 1020
}
GET/api/tokens/auto-topup

Get auto top-up settings and whether the current user can edit them.

PUT/api/tokens/auto-topup

Update auto top-up settings (publisher admins only for publisher-level config).

Request body
{
  "enabled": true,
  "threshold": 10,
  "topUpAmount": 100
}

Error Handling

All error responses return a JSON object with an error field. Use the HTTP status code to determine the error category.

Error response format
{
  "error": "Human-readable error message."
}
StatusMeaning
400Bad request — invalid or missing parameters
401Unauthorized — session missing or expired
402Insufficient tokens for the requested operation
403Forbidden — you do not have access to this resource
404Resource not found
413Payload too large (file exceeds 15 MB)
422Unprocessable — URL could not be fetched or parsed
429Rate limit exceeded — wait before retrying
500Internal server error
504Upstream timeout (e.g. URL fetch)

Rate Limits

Requests are rate-limited per user and per IP to protect service stability. When a limit is reached, the API returns429 Too Many Requests. Wait before retrying.

Endpoint groupScopeBehaviour
API token exchangePer IP30 req / 15 min — prevents brute-force
Login / authPer IPStrict — prevents brute-force
Password resetPer IPStrict — prevents enumeration
Analysis (image & text)Per userModerate — protects external APIs
UploadPer userModerate — prevents abuse
URL extractPer userModerate — limits outbound fetches

Auth Endpoints

POST/api/auth/login

Authenticate and receive a session cookie.

Request body

emailstringrequiredUser's email address
passwordstringrequiredAccount password
Request
POST /api/auth/login
Content-Type: application/json

{
  "email": "reporter@newsroom.com",
  "password": "s3cureP@ssword"
}
Response (200)
{
  "ok": true,
  "role": "USER"
}

A Set-Cookie header with editorial_ai_session is included in the response.

POST/api/auth/logout

Destroy the current session and clear the cookie.

Response (200)
{
  "ok": true
}
POST/api/auth/forgot-password

Request a password reset email. Response is always the same to prevent account enumeration.

emailstringrequiredEmail address on the account
Response (200)
{
  "ok": true,
  "message": "If an account exists, a reset link has been sent."
}
POST/api/auth/reset-password

Reset password using a token from the reset email.

tokenstringrequiredReset token from email link
passwordstringrequiredNew password (min 8 chars, mixed case, digit, special char)
confirmPasswordstringrequiredMust match password
Response (200)
{
  "ok": true
}

Password requirements

  • Minimum 8 characters
  • At least one uppercase and one lowercase letter
  • At least one digit
  • At least one special character (!@#$%^&*_)
  • Cannot reuse any of the last 5 passwords

Admin Endpoints

Publisher lifecycle and administration endpoints for Made By Humans admins. These routes require an authenticated session with ADMIN or SUPERADMIN role, unless explicitly called by cron with Authorization: Bearer $CRON_SECRET.

GET/api/admin/publishers

List all publishers including lifecycle state used by the admin console.

Response (200)
{
  "ok": true,
  "publishers": [
    {
      "id": 1,
      "name": "Example News",
      "emailDomain": "example.com",
      "status": "ACTIVE",
      "tokenBalance": 520,
      "lowBalanceThreshold": 10,
      "suspendedAt": null,
      "deletionScheduledAt": null,
      "deletionDueAt": null,
      "memberCount": 5
    }
  ]
}
POST/api/admin/publishers

Create a publisher account.

namestringrequiredPublisher display name
emailDomainstringOptional domain for auto-assignment
lowBalanceThresholdnumberOptional alert threshold
PATCH/api/admin/publishers/[id]

Update publisher settings (name, domain, low-balance threshold).

POST/api/admin/publishers/[id]

Execute publisher lifecycle actions.

Request body

actionstringrequiredOne of scheduleDeletion or undoDeletion
Schedule deletion (5 working-day grace period)
POST /api/admin/publishers/1
Content-Type: application/json

{
  "action": "scheduleDeletion"
}
Undo pending deletion
POST /api/admin/publishers/1
Content-Type: application/json

{
  "action": "undoDeletion"
}

scheduleDeletion sets status to SUSPENDED, disables publisher usage immediately, and schedules permanent deletion after 5 working days.

DELETE/api/admin/publishers/[id]

Delete publisher immediately, bypassing grace period.

Request body

immediatebooleanrequiredMust be true for immediate irreversible deletion
Request
DELETE /api/admin/publishers/1
Content-Type: application/json

{
  "immediate": true
}
POST/api/admin/publishers/purge

Nightly purge endpoint that deletes publishers whose grace period has elapsed. Scheduled for midnight UTC via platform cron.

Cron request
POST /api/admin/publishers/purge
Authorization: Bearer <CRON_SECRET>

Image Analysis

Image analysis is a multi-step process: upload the image, submit it for AI detection, then poll for results. Optionally request a manipulation heatmap.

Typical workflow:

  1. Upload image → POST /api/storage/upload
  2. Submit for analysis → POST /api/vision/identifai/submit (costs 2 tokens)
  3. Poll until complete → GET /api/vision/identifai/poll
  4. (Bulk workflow) Check up to 10 statuses → GET /api/vision/identifai/batch-status
  5. (Optional) Request heatmap → POST /api/vision/identifai/heatmap (costs 5 tokens)
  6. (Optional) Retrieve heatmap image → GET /api/vision/identifai/heatmap
POST/api/storage/upload

Upload an image file or provide a direct image URL. Returns a submission ID for subsequent analysis.

Request (multipart/form-data)

fileFileImage file (if not providing imageUrl)
imageUrlstringDirect image URL (if not providing file)
titlestringOptional label, max 200 chars
Response (200)
{
  "ok": true,
  "submissionId": "cm4x...",
  "uuidRef": "a1b2c3d4-...",
  "gcsUrl": "https://storage.googleapis.com/...",
  "bytes": 245760,
  "mimeType": "image/jpeg"
}

Constraints

  • Supported formats: JPEG, PNG, WebP, GIF, TIFF, AVIF
  • Maximum file size: 15 MB
  • Magic byte validation is performed on all uploads
  • URL uploads are validated for SSRF (private IPs and non-HTTP schemes are blocked)

No tokens are deducted at upload. Tokens are charged when analysis is submitted.

POST/api/vision/identifai/submit

Submit an uploaded image for AI detection analysis. Deducts 2 tokens from your balance.

submissionIdstringrequiredThe ID returned from upload
Response (200)
{
  "ok": true,
  "submissionId": "cm4x...",
  "identifier": "identifai-job-id",
  "state": "pending"
}

After submitting, poll the results endpoint until state is "completed" or "failed".

GET/api/vision/identifai/poll?submissionId=<id>

Poll for analysis results. Call this every 2–5 seconds until state is no longer "pending".

submissionIdstringrequiredQuery parameter — submission ID
Response (200) — completed
{
  "ok": true,
  "submissionId": "cm4x...",
  "identifier": "identifai-job-id",
  "state": "completed",
  "verdict": "LIKELY_AI",
  "confidence": 0.94,
  "isNsfw": false,
  "hasHeatmap": true,
  "heatmapUrl": null,
  "usage": {
    "creditsUsed": 2,
    "tokensUsed": 2,
    "cost": 0.01,
    "currency": "USD"
  }
}

Verdict values

AUTHENTICImage appears to be a genuine photograph
LIKELY_AIImage is likely AI-generated
PENDINGAnalysis still in progress
GET/api/vision/identifai/batch-status?submissionIds=<id1,id2,...>

Fetch status for up to 10 image submissions in one request. Useful for newsroom bulk queues where multiple uploads are being processed in parallel.

submissionIdsstringrequiredComma-separated list of submission IDs, maximum 10
Response (200)
{
  "ok": true,
  "count": 2,
  "submissions": [
    {
      "submissionId": "cm4x...",
      "uuidRef": "a1b2c3d4...",
      "gcsUrl": "https://storage.googleapis.com/.../image.webp",
      "createdAt": "2026-04-07T12:34:56.000Z",
      "identifier": "identifai-job-id",
      "state": "completed",
      "remoteStatus": "done",
      "verdict": "LIKELY_AI",
      "confidence": 0.94,
      "resultUrl": "https://backend.identifai.net/api/classification/identifai-job-id",
      "isNsfw": false,
      "apiVersion": "v1",
      "hasHeatmap": true,
      "usage": {
        "creditsUsed": 2,
        "tokensUsed": 2,
        "cost": 0.01,
        "currency": "USD"
      },
      "heatmapUrl": null
    }
  ]
}
POST/api/vision/identifai/heatmap

Request a manipulation heatmap for a completed submission. Deducts 5 tokens. The heatmap highlights regions of the image that show signs of manipulation.

submissionIdstringrequiredThe submission ID
Response (200) — ready
{
  "ok": true,
  "submissionId": "cm4x...",
  "state": "ready",
  "heatmapUrl": "https://storage.googleapis.com/..."
}
Response (200) — pending
{
  "ok": true,
  "submissionId": "cm4x...",
  "state": "pending",
  "heatmapIdentifier": "heatmap-job-id"
}

If pending, poll the GET heatmap endpoint until the image is available.

GET/api/vision/identifai/heatmap?submissionId=<id>

Retrieve the heatmap image. Returns a binary image (PNG) with appropriate Content-Typeheader.

submissionIdstringrequiredQuery parameter — submission ID

Status 404 means the heatmap is not ready yet. Cache-Control is set to private, max-age=300.

Text Analysis

Analyse text for AI-generated content. Optionally clean or extract text before analysis. The extract and clean endpoints do not cost tokens.

POST/api/text/pangram

Submit text for AI detection analysis. Costs 2 tokens per 1,000 words.

textstringrequiredText to analyse, 1–20,000 characters
wasCleanedbooleanWhether text was pre-cleaned (default: false)
sourceUrlstringOriginal article URL (stored with submission for traceability)
Request
POST /api/text/pangram
Content-Type: application/json

{
  "text": "Article text to analyse...",
  "wasCleaned": false
}
Response (200)
{
  "ok": true,
  "submissionId": "cm4x...",
  "fractionAi": 0.12,
  "predictionShort": "HUMAN",
  "segments": [
    {
      "text": "First sentence of the article.",
      "prediction": "human",
      "score": 0.05
    },
    {
      "text": "Another sentence that was flagged.",
      "prediction": "ai",
      "score": 0.89
    }
  ]
}

Prediction values

HUMANText appears to be human-written
AIText is likely AI-generated
MIXEDText contains both human and AI content
UNKNOWNInsufficient signal to determine
POST/api/text/extract

Extract article text from a URL using Readability. Useful for pulling content from web pages before analysis. No tokens charged.

urlstringrequiredURL to extract text from (max 2,048 chars)
Response (200)
{
  "ok": true,
  "text": "Extracted article body text...",
  "title": "Article Headline",
  "byline": "By Jane Reporter",
  "excerpt": "First paragraph summary..."
}

Notes

  • SSRF protection blocks private IPs, metadata endpoints, and non-HTTP schemes
  • Maximum page size: 2 MB
  • Fetch timeout: returns 504 if the page takes too long
POST/api/text/clean

Clean and normalise text using AI before submitting for analysis. Removes formatting artefacts, encoding issues, and noise. No tokens charged.

textstringrequiredText to clean, 1–12,000 characters
Response (200)
{
  "ok": true,
  "cleanedText": "Cleaned and normalised text..."
}

Submission History

Retrieve, paginate, and export your analysis history. Both offset-based and cursor-based pagination are supported.

GET/api/submissions/history

Retrieve your submission history with pagination. Returns both image and text submissions ordered by date (newest first).

Query parameters

pagenumberPage number, starting from 1 (default: 1)
limitnumberResults per page, 1–100 (default: 10)
cursorstringCursor for cursor-based pagination (alternative to page)
Response (200)
{
  "ok": true,
  "submissions": [
    {
      "id": "cm4x...",
      "type": "IMAGE",
      "createdAt": "2026-04-06T12:00:00.000Z",
      "originalFilename": "photo.jpg",
      "gcsUrl": "https://storage.googleapis.com/...",
      "sourceUrl": "https://example.com/photo.jpg",
      "verdict": "AUTHENTIC",
      "confidence": 0.97,
      "isNsfw": false,
      "heatmapUrl": null,
      "tokenCost": 2
    },
    {
      "id": "cm4y...",
      "type": "TEXT",
      "createdAt": "2026-04-06T11:30:00.000Z",
      "originalFilename": null,
      "gcsUrl": null,
      "sourceUrl": "https://example.com/article",
      "verdict": "HUMAN",
      "confidence": 0.08,
      "isNsfw": null,
      "heatmapUrl": null,
      "tokenCost": 2,
      "text": "Original text submitted...",
      "segments": [...]
    }
  ],
  "page": 1,
  "totalPages": 5,
  "totalCount": 47,
  "nextCursor": "cm4x..."
}

Pagination

Use page and limit for offset-based pagination. The response includes totalPages and totalCount so you can render page controls.

Alternatively, pass nextCursor from a previous response as the cursor parameter for cursor-based pagination. When nextCursor is null, there are no more results.

Client-side sorting

Results are returned ordered by createdAt descending. The dashboard UI supports client-side sorting on any column: type, createdAt, verdict, confidence, isNsfw, and tokenCost.

Submission fields

FieldTypeDescription
idstringUnique submission ID
typestringIMAGE or TEXT
createdAtstringISO 8601 timestamp
originalFilenamestring | nullOriginal uploaded filename
gcsUrlstring | nullInternal storage URL for the uploaded image
sourceUrlstring | nullOriginal source URL — the publication image URL (IMAGE) or article URL (TEXT) if provided
verdictstring | nullAnalysis verdict (e.g. AUTHENTIC, LIKELY_AI, HUMAN)
confidencenumber | nullConfidence score (0–1)
isNsfwboolean | nullWhether the image is flagged as NSFW
heatmapUrlstring | nullURL to the manipulation heatmap image
tokenCostnumberTokens consumed by this analysis
textstring | nullOriginal text (TEXT type only)
segmentsarrayPer-sentence AI scores (TEXT type only)
GET/api/submissions/history/export

Export your full submission history as a CSV file. The response is a downloadable CSV with columns for date, type, filename, verdict, confidence, NSFW flag, token cost, image URL, and heatmap URL.

Response

Returns a text/csv response with Content-Disposition: attachment. No query parameters are required — all submissions for the authenticated user are included.

CSV columns
Date,Type,Filename,Verdict,Confidence,NSFW,Tokens,Source URL,Heatmap URL
2026-04-06T12:00:00.000Z,IMAGE,photo.jpg,AUTHENTIC,97.0%,False,2,https://example.com/photo.jpg,
2026-04-06T11:30:00.000Z,TEXT,,HUMAN,8.0%,,2,https://example.com/article,
Example: Download CSV (cURL)
curl https://made.by.humans.press/api/submissions/history/export \
  -b cookies.txt \
  -o submissions.csv

AI Integration Quickstart

This checklist is designed for autonomous agents and deterministic client integrations.

  1. Exchange your API User ID and API Key for a bearer token via /api/v1/auth/token.
  2. Include Authorization: Bearer <token> on all requests.
  3. Check balance first with /api/tokens/balance.
  4. On 401, request a new bearer token (the previous one may have expired).
  5. On 402, do not retry analysis; surface insufficient tokens and stop.
  6. For image polling, respect nextPollInMs from /api/vision/identifai/poll. If absent, wait 9 seconds.
  7. Retry transient errors only: 429, 500, 502, 503, 504.
  8. Use exponential backoff with jitter and cap to 60 seconds.
  9. Stop after 20 poll attempts or 10 minutes total, whichever happens first.
  10. Treat state: "completed" and state: "failed" as terminal.
Recommended retry policy (pseudo-code)
if status == 401:
  token = POST /api/v1/auth/token  // refresh bearer token
  retry with new token
else if status in [429, 500, 502, 503, 504]:
  wait = min(60000, baseDelayMs * 2^attempt) + random(0..500)
  retry
else if status == 402:
  stop("insufficient tokens")
else if status >= 400:
  stop("non-retryable request error")

for pollAttempt in 1..20:
  res = GET /api/vision/identifai/poll?submissionId=...
  if res.state in ["completed", "failed"]:
    return res
  sleep(res.nextPollInMs || 9000)

stop("poll timeout")

A machine-readable spec is available at /api/openapi.json.

Advanced Analysis

Phase II capabilities for deeper content verification: editorial interrogation, authorship fingerprinting, video/audio detection, metadata forensics, and bulk scanning.

POST/api/text/interrogate

AI-powered forensic editorial review. Returns a structured pre-publication checklist identifying unverified claims, unsourced statistics, logical issues, and boilerplate flags. Costs 3 tokens.

textstringrequiredArticle text, 100–30,000 characters
titlestringArticle title (improves context)
Response (200)
{
  "ok": true,
  "tokensCost": 3,
  "analysis": {
    "summary": "Article contains two unverified claims...",
    "risk_level": "medium",
    "unverified_claims": [
      {
        "text": "Studies show 80% of readers...",
        "concern": "No source cited for this statistic",
        "suggestion": "Ask author for the specific study"
      }
    ],
    "statistical_assertions": [...],
    "logical_issues": [...],
    "boilerplate_flags": [...],
    "verification_questions": [
      "Can you provide the source for the 80% figure?"
    ],
    "style_notes": [...]
  }
}
POST/api/text/fingerprint

Authorship fingerprinting. Identifies likely AI model, analyses writing style metrics, and detects mixed human/AI authorship. Costs 3 tokens.

textstringrequiredText to fingerprint, 200–25,000 characters
Response (200)
{
  "ok": true,
  "tokensCost": 3,
  "fingerprint": {
    "overallAiScore": 0.72,
    "prediction": "AI",
    "likelyModel": { "name": "ChatGPT-4 / GPT-4o (likely)", "confidence": "moderate" },
    "mixedAuthorship": true,
    "mixedAuthorshipLikelihood": 0.45,
    "styleMetrics": {
      "lexicalDiversity": 0.412,
      "avgSentenceLength": 19.3,
      "sentenceLengthVariance": 3.2,
      "transitionDensity": 9.1,
      "paragraphUniformity": 0.78,
      "wordCount": 1200,
      "sentenceCount": 62,
      "paragraphCount": 8
    },
    "interpretation": [
      "This text shows strong indicators of AI generation.",
      "Unusually high density of formal transition words.",
      "Very uniform sentence lengths."
    ]
  }
}
POST/api/vision/video

Submit a video file or URL for deepfake/AI detection. Costs 8 tokens.

Request (multipart/form-data)

fileFileVideo file (if not providing videoUrl)
videoUrlstringPublic video URL (if not providing file)
Response (200)
{
  "ok": true,
  "tokensCost": 8,
  "identifier": "vid_abc123",
  "state": "completed",
  "verdict": "artificial",
  "confidence": 0.89
}

Constraints

  • Supported upload formats: MP4, MOV, WEBM, AVI, MKV, MPEG
  • Maximum upload size: 100 MB
  • If file exceeds 100 MB, provide a direct URL instead
  • Provide either file or videoUrl, not both
POST/api/audio/verify

Submit an audio file or URL for synthetic voice / clone detection. Costs 4 tokens.

Request (multipart/form-data)

fileFileAudio file (if not providing audioUrl)
audioUrlstringPublic audio URL (if not providing file)
Response (200)
{
  "ok": true,
  "tokensCost": 4,
  "identifier": "aud_xyz789",
  "state": "completed",
  "verdict": "human",
  "confidence": 0.94,
  "isCloned": false
}

Constraints

  • Supported upload formats: MP3, WAV, M4A, AAC, FLAC, OGG, WEBM
  • Maximum upload size: 100 MB
  • If file exceeds 100 MB, provide a direct URL instead
  • Provide either file or audioUrl, not both
POST/api/vision/metadata

Extract and analyse EXIF/metadata from an image submission. Checks for camera info, editing software, C2PA credentials, and suspicious indicators. Costs 3 tokens.

submissionIdstringrequiredID of an existing IMAGE submission
Response (200)
{
  "ok": true,
  "submissionId": "cm4x...",
  "tokensCost": 3,
  "metadata": {
    "camera": "Canon EOS R5",
    "software": "Adobe Lightroom",
    "dateOriginal": "2026-03-15T14:30:00",
    "gps": null,
    "hasBeenEdited": true,
    "editingSoftware": ["Adobe Lightroom"],
    "metadataStripped": false,
    "c2pa": null,
    "suspiciousIndicators": []
  }
}

Monitoring & Bulk Scanning

Proactive content monitoring via RSS feeds and bulk archive scanning.

GET/api/watchdog/feeds

List your configured monitoring feeds.

Response (200)
{
  "ok": true,
  "feeds": [
    { "id": "clu...", "url": "https://example.com/feed.xml", "label": "My Publication", "type": "rss" }
  ]
}
POST/api/watchdog/feeds

Add a new RSS feed to monitor (max 10 per account).

urlstringrequiredRSS or Atom feed URL
labelstringDisplay label for this feed
typestringFeed type: rss (default) or atom
POST/api/watchdog/scan

Trigger a scan of all monitored feeds. Extracts latest articles and runs AI detection. Costs 1 token per article scanned.

Response (200)
{
  "ok": true,
  "feedsScanned": 3,
  "totalArticlesScanned": 24,
  "flagged": 2,
  "results": [
    {
      "feedUrl": "https://example.com/feed.xml",
      "label": "My Publication",
      "status": "completed",
      "articles": [
        { "url": "...", "title": "...", "fractionAi": 0.08, "prediction": "HUMAN", "flagged": false },
        { "url": "...", "title": "...", "fractionAi": 0.72, "prediction": "AI", "flagged": true }
      ]
    }
  ]
}
POST/api/bulk/scan

Batch-scan multiple URLs or a sitemap. Extracts article text and runs AI detection on each. Costs 1 token per article. Maximum 50 URLs per request.

urlsstring[]Array of article URLs to scan
sitemapUrlstringSitemap URL (alternative to urls array)
Response (200)
{
  "ok": true,
  "tokensCost": 12,
  "totalUrls": 15,
  "processed": 12,
  "failed": 3,
  "results": [
    { "url": "...", "status": "completed", "title": "...", "wordCount": 1200, "fractionAi": 0.05, "prediction": "HUMAN" },
    { "url": "...", "status": "error", "error": "HTTP 404" }
  ]
}

Compliance & Certification

Generate compliance reports and manage publisher trust certification.

GET/api/compliance/report

Generate a compliance report for a date range. No tokens charged.

fromstringStart date (ISO 8601, default: 30 days ago)
tostringEnd date (ISO 8601, default: now)
formatstringResponse format: json (default) or csv
Response (200)
{
  "ok": true,
  "report": {
    "generatedAt": "2026-05-25T10:00:00.000Z",
    "period": { "from": "...", "to": "..." },
    "publisher": "Press Gazette",
    "summary": {
      "totalSubmissions": 142,
      "imageChecks": 89,
      "textChecks": 53,
      "verifiedArticles": 31,
      "aiDetectedImages": 4,
      "aiDetectedText": 7
    },
    "submissions": [...],
    "verificationBadges": [...]
  }
}
GET/api/publisher/certification

Get current trust certification status and metrics for your publisher.

Response (200)
{
  "ok": true,
  "publisher": "Press Gazette",
  "certification": {
    "level": "Gold",
    "badge": "gold",
    "description": "This publication regularly verifies content authenticity...",
    "metrics": { "totalChecks": 142, "verifiedArticles": 31, "recentActivity": 28 },
    "embedCode": "<script src=\"https://made.by.humans.press/embed/trust-badge.js\" ...></script>",
    "publicUrl": "https://made.by.humans.press/certification/1"
  }
}
POST/api/submissions/inbox

Submit content for editorial review via a share token. Used by freelancers/contributors to submit articles directly into the editorial queue. No authentication required (uses share token).

shareTokenstringrequiredShare token provided by the editorial team
contributorNamestringName of the submitting contributor
contributorEmailstringEmail of the contributor
titlestringArticle title
textstringArticle text
imageUrlsstring[]Array of image URLs (max 10)

Integration Workflows

Below are complete examples showing how to integrate Made By Humans into your application. These workflows are the basis for building a WordPress plugin or any CMS integration.

Full image verification flow (API key auth)

cURL — complete image analysis
# 1. Get bearer token
curl -X POST https://made.by.humans.press/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"apiUserId": "MBH-A3K7NW2P", "apiKey": "mbh_your_api_key"}'
# → { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }

# 2. Upload image
curl -X POST https://made.by.humans.press/api/storage/upload \
  -H "Authorization: Bearer eyJ..." \
  -F "file=@photo.jpg" \
  -F "title=Photo for verification"
# → returns { "submissionId": "cm4x..." }

# 3. Submit for AI detection (costs 2 tokens)
curl -X POST https://made.by.humans.press/api/vision/identifai/submit \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"submissionId": "cm4x..."}'

# 4. Poll for results (repeat until state != "pending")
curl "https://made.by.humans.press/api/vision/identifai/poll?submissionId=cm4x..." \
  -H "Authorization: Bearer eyJ..."
# → returns { "state": "completed", "verdict": "AUTHENTIC", ... }

# 5. (Optional) Request heatmap (costs 5 tokens)
curl -X POST https://made.by.humans.press/api/vision/identifai/heatmap \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"submissionId": "cm4x..."}'

Full text verification flow (API key auth)

cURL — complete text analysis
# 1. Get bearer token
curl -X POST https://made.by.humans.press/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"apiUserId": "MBH-A3K7NW2P", "apiKey": "mbh_your_api_key"}'
# → { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }

# 2. (Optional) Extract article text from URL (free)
curl -X POST https://made.by.humans.press/api/text/extract \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/article"}'
# → returns { "text": "Article body text..." }

# 3. (Optional) Clean text before analysis (free)
curl -X POST https://made.by.humans.press/api/text/clean \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Raw text with formatting artifacts..."}'
# → returns { "cleanedText": "Clean text..." }

# 4. Analyse text (costs 2 tokens per 1,000 words)
curl -X POST https://made.by.humans.press/api/text/pangram \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"text": "The article text to analyse...", "wasCleaned": true}'
# → returns { "fractionAi": 0.08, "predictionShort": "HUMAN", ... }

WordPress plugin integration notes

When building a WordPress plugin on this API:

  • Credentials setup: In the plugin settings page, ask users for their API User ID and API Key. Both are generated from the Made By Humans Account page.
  • Token management: Exchange the API credentials for a bearer token using POST /api/v1/auth/token. Cache the token in a WordPress transient with a 50-minute expiry (tokens last 1 hour). Refresh automatically on 401.
  • Auth header: Include Authorization: Bearer <token> on all API calls using wp_remote_post() / wp_remote_get().
  • Multiple keys: Users can generate separate keys for production and staging environments, each with a descriptive label.
  • Image uploads: Use wp_remote_post() with the file as a multipart form body.
  • Polling: Use a JavaScript polling loop on the client side or a WordPress cron/AJAX handler to check analysis status.
  • Token checking: Before submitting analysis, verify the user has sufficient tokens by checking the 402 response, or maintain a cached balance from GET /api/tokens/balance.
  • Error handling: Always check the HTTP status code. Display user-readable messages from the error field. On 401, refresh the bearer token and retry once.

Ready to integrate?

Create an account, generate your API credentials, and start verifying content from your WordPress site or CMS.