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
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.
Create an account
Sign up with your name, email, and publication. You can also use the web tool immediately after signing up.
Ensure you have tokens
You need a positive token balance to generate API credentials. Check your balance on the Account page.
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)
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.
POST /api/v1/auth/token
Content-Type: application/json
{
"apiUserId": "MBH-A3K7NW2P",
"apiKey": "mbh_your_api_key_here"
}{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}Make API requests
Include the bearer token in the Authorization header of every API request.
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.
- Generate an API User ID and API Key from the Account page.
- POST to
/api/v1/auth/tokenwith yourapiUserIdandapiKeyto receive a bearer token. - Include
Authorization: Bearer <token>on all subsequent API requests. - When the token expires (1 hour), request a new one using the same credentials.
| Token endpoint | POST /api/v1/auth/token |
| Token format | HS256 JWT |
| Token lifetime | 1 hour |
| Header format | Authorization: Bearer <token> |
| Rate limit | 30 requests per 15 minutes per IP |
# 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.
- POST to
/api/auth/loginwith your email and password. - The server responds with a
Set-Cookieheader containing aneditorial_ai_sessionJWT. - Include this cookie in all subsequent API requests.
| Cookie name | editorial_ai_session |
| Algorithm | HS256 (JWT) |
| Lifetime | 7 days |
| HttpOnly | Yes |
| Secure | Yes (production) |
| SameSite | Lax |
# 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.txtAPI 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.
/api/v1/auth/tokenExchange 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...)POST /api/v1/auth/token
Content-Type: application/json
{
"apiUserId": "MBH-A3K7NW2P",
"apiKey": "mbh_your_api_key_here"
}{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}{
"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
/api/api-keysList all API keys for the authenticated user. Also returns the API User ID and usage statistics. Requires session auth.
{
"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 } }
]
}
}/api/api-keysGenerate 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")POST /api/api-keys
Content-Type: application/json
{
"label": "WordPress Production"
}{
"ok": true,
"apiUserId": "MBH-A3K7NW2P",
"key": {
"id": "cm7x...",
"rawKey": "mbh_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8",
"keyPrefix": "mbh_a1b2c3d4",
"label": "WordPress Production",
"createdAt": "2026-04-17T10:00:00.000Z"
}
}rawKeyis only returned at creation time. Store it securely — it cannot be retrieved again./api/api-keys/[id]Update the label on an existing API key. Requires session auth.
labelstringrequiredNew label for the keyPATCH /api/api-keys/cm7x...
Content-Type: application/json
{
"label": "WordPress Staging"
}/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.
DELETE /api/api-keys/cm7x...{
"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.
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.
/api/token-costsReturns 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.
{
"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 detectionimage_heatmap— tokens per manipulation heatmapweb_detection— tokens per reverse image search
Costs are configurable by admins and cached server-side for up to one minute.
/api/tokens/balanceGet the current effective token balance and forecast data.
{
"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
}
}/api/tokens/transactions?page=1&limit=25List token transactions for the current user or active publisher.
{
"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
}/api/tokens/purchaseList available token packages.
{
"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" }
]
}/api/tokens/purchaseAdd a package of tokens. Payment provider wiring is not enabled yet, so this grants tokens directly in the current phase.
packageIdstringrequiredOne of starter, standard, bulkPOST /api/tokens/purchase
Content-Type: application/json
{
"packageId": "standard"
}{
"ok": true,
"note": "Payment processing not yet integrated. Tokens added for testing.",
"package": "€20 — 500 tokens",
"tokensAdded": 500,
"newBalance": 1020
}/api/tokens/auto-topupGet auto top-up settings and whether the current user can edit them.
/api/tokens/auto-topupUpdate auto top-up settings (publisher admins only for publisher-level config).
{
"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": "Human-readable error message."
}| Status | Meaning |
|---|---|
| 400 | Bad request — invalid or missing parameters |
| 401 | Unauthorized — session missing or expired |
| 402 | Insufficient tokens for the requested operation |
| 403 | Forbidden — you do not have access to this resource |
| 404 | Resource not found |
| 413 | Payload too large (file exceeds 15 MB) |
| 422 | Unprocessable — URL could not be fetched or parsed |
| 429 | Rate limit exceeded — wait before retrying |
| 500 | Internal server error |
| 504 | Upstream 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 group | Scope | Behaviour |
|---|---|---|
| API token exchange | Per IP | 30 req / 15 min — prevents brute-force |
| Login / auth | Per IP | Strict — prevents brute-force |
| Password reset | Per IP | Strict — prevents enumeration |
| Analysis (image & text) | Per user | Moderate — protects external APIs |
| Upload | Per user | Moderate — prevents abuse |
| URL extract | Per user | Moderate — limits outbound fetches |
Auth Endpoints
/api/auth/loginAuthenticate and receive a session cookie.
Request body
emailstringrequiredUser's email addresspasswordstringrequiredAccount passwordPOST /api/auth/login
Content-Type: application/json
{
"email": "reporter@newsroom.com",
"password": "s3cureP@ssword"
}{
"ok": true,
"role": "USER"
}A Set-Cookie header with editorial_ai_session is included in the response.
/api/auth/logoutDestroy the current session and clear the cookie.
{
"ok": true
}/api/auth/forgot-passwordRequest a password reset email. Response is always the same to prevent account enumeration.
emailstringrequiredEmail address on the account{
"ok": true,
"message": "If an account exists, a reset link has been sent."
}/api/auth/reset-passwordReset password using a token from the reset email.
tokenstringrequiredReset token from email linkpasswordstringrequiredNew password (min 8 chars, mixed case, digit, special char)confirmPasswordstringrequiredMust match password{
"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.
/api/admin/publishersList all publishers including lifecycle state used by the admin console.
{
"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
}
]
}/api/admin/publishersCreate a publisher account.
namestringrequiredPublisher display nameemailDomainstringOptional domain for auto-assignmentlowBalanceThresholdnumberOptional alert threshold/api/admin/publishers/[id]Update publisher settings (name, domain, low-balance threshold).
/api/admin/publishers/[id]Execute publisher lifecycle actions.
Request body
actionstringrequiredOne of scheduleDeletion or undoDeletionPOST /api/admin/publishers/1
Content-Type: application/json
{
"action": "scheduleDeletion"
}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.
/api/admin/publishers/[id]Delete publisher immediately, bypassing grace period.
Request body
immediatebooleanrequiredMust be true for immediate irreversible deletionDELETE /api/admin/publishers/1
Content-Type: application/json
{
"immediate": true
}/api/admin/publishers/purgeNightly purge endpoint that deletes publishers whose grace period has elapsed. Scheduled for midnight UTC via platform cron.
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:
- Upload image →
POST /api/storage/upload - Submit for analysis →
POST /api/vision/identifai/submit(costs 2 tokens) - Poll until complete →
GET /api/vision/identifai/poll - (Bulk workflow) Check up to 10 statuses →
GET /api/vision/identifai/batch-status - (Optional) Request heatmap →
POST /api/vision/identifai/heatmap(costs 5 tokens) - (Optional) Retrieve heatmap image →
GET /api/vision/identifai/heatmap
/api/storage/uploadUpload 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{
"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.
/api/vision/identifai/submitSubmit an uploaded image for AI detection analysis. Deducts 2 tokens from your balance.
submissionIdstringrequiredThe ID returned from upload{
"ok": true,
"submissionId": "cm4x...",
"identifier": "identifai-job-id",
"state": "pending"
}After submitting, poll the results endpoint until state is "completed" or "failed".
/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{
"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
| AUTHENTIC | Image appears to be a genuine photograph |
| LIKELY_AI | Image is likely AI-generated |
| PENDING | Analysis still in progress |
/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{
"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
}
]
}/api/vision/identifai/heatmapRequest 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{
"ok": true,
"submissionId": "cm4x...",
"state": "ready",
"heatmapUrl": "https://storage.googleapis.com/..."
}{
"ok": true,
"submissionId": "cm4x...",
"state": "pending",
"heatmapIdentifier": "heatmap-job-id"
}If pending, poll the GET heatmap endpoint until the image is available.
/api/vision/identifai/heatmap?submissionId=<id>Retrieve the heatmap image. Returns a binary image (PNG) with appropriate Content-Typeheader.
submissionIdstringrequiredQuery parameter — submission IDStatus 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.
/api/text/pangramSubmit text for AI detection analysis. Costs 2 tokens per 1,000 words.
textstringrequiredText to analyse, 1–20,000 characterswasCleanedbooleanWhether text was pre-cleaned (default: false)sourceUrlstringOriginal article URL (stored with submission for traceability)POST /api/text/pangram
Content-Type: application/json
{
"text": "Article text to analyse...",
"wasCleaned": false
}{
"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
| HUMAN | Text appears to be human-written |
| AI | Text is likely AI-generated |
| MIXED | Text contains both human and AI content |
| UNKNOWN | Insufficient signal to determine |
/api/text/extractExtract 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){
"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
/api/text/cleanClean 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{
"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.
/api/submissions/historyRetrieve 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){
"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
| Field | Type | Description |
|---|---|---|
id | string | Unique submission ID |
type | string | IMAGE or TEXT |
createdAt | string | ISO 8601 timestamp |
originalFilename | string | null | Original uploaded filename |
gcsUrl | string | null | Internal storage URL for the uploaded image |
sourceUrl | string | null | Original source URL — the publication image URL (IMAGE) or article URL (TEXT) if provided |
verdict | string | null | Analysis verdict (e.g. AUTHENTIC, LIKELY_AI, HUMAN) |
confidence | number | null | Confidence score (0–1) |
isNsfw | boolean | null | Whether the image is flagged as NSFW |
heatmapUrl | string | null | URL to the manipulation heatmap image |
tokenCost | number | Tokens consumed by this analysis |
text | string | null | Original text (TEXT type only) |
segments | array | Per-sentence AI scores (TEXT type only) |
/api/submissions/history/exportExport 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.
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,curl https://made.by.humans.press/api/submissions/history/export \
-b cookies.txt \
-o submissions.csvAI Integration Quickstart
This checklist is designed for autonomous agents and deterministic client integrations.
- Exchange your API User ID and API Key for a bearer token via
/api/v1/auth/token. - Include
Authorization: Bearer <token>on all requests. - Check balance first with
/api/tokens/balance. - On
401, request a new bearer token (the previous one may have expired). - On
402, do not retry analysis; surface insufficient tokens and stop. - For image polling, respect
nextPollInMsfrom/api/vision/identifai/poll. If absent, wait 9 seconds. - Retry transient errors only:
429,500,502,503,504. - Use exponential backoff with jitter and cap to 60 seconds.
- Stop after 20 poll attempts or 10 minutes total, whichever happens first.
- Treat
state: "completed"andstate: "failed"as terminal.
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.
/api/text/interrogateAI-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 characterstitlestringArticle title (improves context){
"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": [...]
}
}/api/text/fingerprintAuthorship 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{
"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."
]
}
}/api/vision/videoSubmit 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){
"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
fileorvideoUrl, not both
/api/audio/verifySubmit 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){
"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
fileoraudioUrl, not both
/api/vision/metadataExtract 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{
"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.
/api/watchdog/feedsList your configured monitoring feeds.
{
"ok": true,
"feeds": [
{ "id": "clu...", "url": "https://example.com/feed.xml", "label": "My Publication", "type": "rss" }
]
}/api/watchdog/feedsAdd a new RSS feed to monitor (max 10 per account).
urlstringrequiredRSS or Atom feed URLlabelstringDisplay label for this feedtypestringFeed type: rss (default) or atom/api/watchdog/scanTrigger a scan of all monitored feeds. Extracts latest articles and runs AI detection. Costs 1 token per article scanned.
{
"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 }
]
}
]
}/api/bulk/scanBatch-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 scansitemapUrlstringSitemap URL (alternative to urls array){
"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.
/api/compliance/reportGenerate 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{
"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": [...]
}
}/api/publisher/certificationGet current trust certification status and metrics for your publisher.
{
"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"
}
}/api/submissions/inboxSubmit 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 teamcontributorNamestringName of the submitting contributorcontributorEmailstringEmail of the contributortitlestringArticle titletextstringArticle textimageUrlsstring[]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)
# 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)
# 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 IDandAPI 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 on401. - Auth header: Include
Authorization: Bearer <token>on all API calls usingwp_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
errorfield. On401, 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.