The EngageCheck API
Vet a whole roster of creators before a dollar leaves the budget. The same five-signal analysis behind our dashboard, exposed as clean JSON: audit one handle or fire fifty at once, get an audience quality score from 0 to 100, fake-follower percentage, bot and pod flags, and the real engagement rate. Submit an audit and get a report ID back instantly. Poll for the result or receive it via webhook. Built for agencies running creator due diligence at scale.
https://engagecheck.com/api/v1Public per-report pricing
Every plan and every pack includes full API access. A credit is a credit, whether you spend it in the dashboard or over the API, and credits never expire. There is no separate API tier, no sales call, and no annual commitment: create an account, buy credits, generate a key, and start auditing.
Credits arrive with each monthly invoice and roll over forever. Cancel anytime in one click via the Stripe portal and keep every unused credit.
$1.40 per report on Pro down to $1.00 per report on Scale.
No subscription required. Buy a pack when you need it; credits never expire and the API draws from the same balance.
$5.00 for a single report down to $1.20 per report on Agency 500.
- Batch endpoint: up to 50 handles per call, with per-handle webhooks.
- Failed, private, or not-found audits are refunded automatically - you only pay for results.
- Rate limit: 60 requests per minute per key by default. Need more? Contact us.
- Self-serve keys: create and revoke them at /account/api - no approval queue.
| API | How you get access | Pricing |
|---|---|---|
| EngageCheck | Self-serve keys, no sales call | Monthly plans from $49/mo, or one-off packs. $1.00 to $5.00 per report, all pricing public. |
| Modash Discovery API | Annual contract; no monthly or pay-as-you-go plans | Starts at $16,200 per year, per their public pricing page. Source: modash.io API pricing |
| HypeAuditor API | Via sales: request a demo to discuss access | No public API pricing listed on their site. Source: hypeauditor.com API page |
Competitor details reflect their public pages as of July 2026; follow the source links for their current terms.
Authenticate with a Bearer key
Every request must carry your API key. Send it in the Authorization header as a Bearer token, or in an X-API-Key header if your client cannot set Authorization. Keys look like ec_live_... and draw from your account's credit balance with a per-minute rate limit. Keys are self-serve: sign in and create or revoke them at /account/api - every account can, no approval queue. Treat your key like a password and never ship it in client-side code.
Authorization: Bearer ec_live_8Fq2Zx...X-API-Key: ec_live_8Fq2Zx.../auditsRun a single audit
Submit one Instagram handle for analysis. The call returns 202 Accepted immediately with a report id and a poll_url, then the scrape, analysis, and scoring run in the background, usually 30 to 90 seconds. Poll GET /api/v1/audits/{id} until status is complete or error, or pass a webhook_url to have the finished report delivered to you. This consumes one credit. If the scrape fails or the account is private, the credit is automatically refunded and the report comes back with an error status. If we already hold a fresh report for this handle, the response returns instantly with status complete and the full report inline.
| Field | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | Instagram handle to audit. The @, full URLs, and casing are all normalized. |
| webhook_url | string | No | HTTPS endpoint to receive the result. Fires audit.completed on success, audit.failed on failure. |
# 1. Submit the audit. Returns 202 Accepted right away with a report id.
curl -X POST https://engagecheck.com/api/v1/audits \
-H "Authorization: Bearer ec_live_xxx" \
-H "Content-Type: application/json" \
-d '{"username": "natgeo"}'
# 2. Poll the report id until status is complete or error.
curl https://engagecheck.com/api/v1/audits/142 \
-H "Authorization: Bearer ec_live_xxx"// 1. Submit the audit. Returns 202 immediately with a report id.
const submit = await fetch("https://engagecheck.com/api/v1/audits", {
method: "POST",
headers: {
"Authorization": "Bearer ec_live_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({ username: "natgeo" }),
});
const { id, poll_url } = await submit.json();
// 2. Poll until the audit is complete or errors out (usually 30 to 90s).
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let report;
while (true) {
const res = await fetch("https://engagecheck.com" + poll_url, {
headers: { "Authorization": "Bearer ec_live_xxx" },
});
report = await res.json();
if (report.status === "complete" || report.status === "error") break;
await sleep(3000);
}
if (report.status === "complete") {
console.log(report.aqs, report.verdict);
} else {
console.error(report.error_message);
}import time
import requests
# 1. Submit the audit. Returns 202 immediately with a report id.
submit = requests.post(
"https://engagecheck.com/api/v1/audits",
headers={"Authorization": "Bearer ec_live_xxx"},
json={"username": "natgeo"},
)
poll_url = submit.json()["poll_url"]
# 2. Poll until the audit is complete or errors out (usually 30 to 90s).
while True:
res = requests.get(
"https://engagecheck.com" + poll_url,
headers={"Authorization": "Bearer ec_live_xxx"},
)
report = res.json()
if report["status"] in ("complete", "error"):
break
time.sleep(3)
if report["status"] == "complete":
print(report["aqs"], report["verdict"])
else:
print(report["error_message"]){
"id": 142,
"username": "natgeo",
"status": "pending",
"credits_remaining": 249,
"poll_url": "/api/v1/audits/142",
"webhook_url": null
}{
"id": 142,
"username": "natgeo",
"status": "complete",
"aqs": 88,
"verdict": "green",
"report": {
"username": "natgeo",
"aqs": 88,
"verdict": "green",
"summary": "@natgeo scored 88 out of 100, indicating a largely authentic audience...",
"signals": [
{
"key": "engagement_rate",
"label": "Engagement Rate",
"score": 92,
"weightPct": 25,
"verdict": "green",
"value": "3.10% ER",
"detail": "Engagement sits inside the healthy 1% to 10% band."
}
],
"commentQuality": {
"bot_comment_percentage": 4,
"pod_activity_detected": false,
"linguistic_summary": "Comments read as genuine conversation."
},
"profile": {
"fullName": "National Geographic",
"followersCount": 280000000,
"followingCount": 142,
"postsCount": 29000,
"isPrivate": false,
"isVerified": true
},
"generatedAt": "2026-06-25T17:00:00.000Z"
},
"credits_remaining": 249,
"poll_url": "/api/v1/audits/142",
"webhook_url": null
}Send a GET to the poll_url until status reaches complete or error. While the audit runs it reports an interim status like scraping or analyzing.
curl https://engagecheck.com/api/v1/audits/142 \
-H "Authorization: Bearer ec_live_xxx"{
"id": 142,
"username": "natgeo",
"status": "analyzing",
"error_message": null,
"created_at": "2026-06-25 17:00:00",
"completed_at": null
}/audits/batchBatch audits
The agency workflow: audit up to 50 handles in one call. Credits for the whole batch are reserved up front, so the request returns 402 if the key cannot cover it. The call returns 202 Accepted immediately with an array of report ids and poll_urls, then the audits process sequentially in the background. Poll each id, or pass a webhook_url to receive one webhook per handle as each one completes. Any audit that fails comes back with an error status and is refunded one credit, so you only pay for results.
| Field | Type | Required | Description |
|---|---|---|---|
| usernames | string[] | Yes | Array of Instagram handles, maximum 50. Duplicates and blanks are dropped. |
| webhook_url | string | No | HTTPS endpoint to receive results. Fires one webhook per handle: audit.completed or audit.failed. |
# Submit up to 50 handles. Returns 202 with a report id per handle.
curl -X POST https://engagecheck.com/api/v1/audits/batch \
-H "Authorization: Bearer ec_live_xxx" \
-H "Content-Type: application/json" \
-d '{"usernames": ["natgeo", "nasa"]}'
# Then poll each returned id until it is complete or error.
curl https://engagecheck.com/api/v1/audits/143 \
-H "Authorization: Bearer ec_live_xxx"// Submit the batch. Returns 202 immediately with one id per handle.
const submit = await fetch("https://engagecheck.com/api/v1/audits/batch", {
method: "POST",
headers: {
"Authorization": "Bearer ec_live_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({ usernames: ["natgeo", "nasa"] }),
});
const { results } = await submit.json();
// Each row carries its own poll_url. Poll them until every audit settles.
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function poll(pollUrl) {
while (true) {
const res = await fetch("https://engagecheck.com" + pollUrl, {
headers: { "Authorization": "Bearer ec_live_xxx" },
});
const report = await res.json();
if (report.status === "complete" || report.status === "error") return report;
await sleep(3000);
}
}
const reports = await Promise.all(results.map((r) => poll(r.poll_url)));
for (const r of reports) console.log(r.username, r.aqs ?? r.error_message);import time
import requests
submit = requests.post(
"https://engagecheck.com/api/v1/audits/batch",
headers={"Authorization": "Bearer ec_live_xxx"},
json={"usernames": ["natgeo", "nasa"]},
)
results = submit.json()["results"]
def poll(poll_url):
while True:
res = requests.get(
"https://engagecheck.com" + poll_url,
headers={"Authorization": "Bearer ec_live_xxx"},
)
report = res.json()
if report["status"] in ("complete", "error"):
return report
time.sleep(3)
for row in results:
report = poll(row["poll_url"])
print(report["username"], report.get("aqs", report.get("error_message"))){
"results": [
{ "id": 143, "username": "natgeo", "status": "pending", "poll_url": "/api/v1/audits/143" },
{ "id": 144, "username": "nasa", "status": "pending", "poll_url": "/api/v1/audits/144" }
],
"credits_remaining": 247,
"webhook_url": null
}/auditsList recent reports
Page through the most recent reports as a lightweight summary array. Read-only, so it never consumes a credit.
| Field | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Rows to return, 1 to 100. Defaults to 25. |
| offset | integer | No | Rows to skip for pagination. Defaults to 0. |
curl "https://engagecheck.com/api/v1/audits?limit=25&offset=0" \
-H "Authorization: Bearer ec_live_xxx"{
"data": [
{
"id": 144,
"username": "nasa",
"status": "complete",
"aqs": 91,
"verdict": "green",
"created_at": "2026-06-25 17:01:00",
"completed_at": "2026-06-25 17:01:38"
}
],
"count": 1,
"limit": 25,
"offset": 0
}/audits/{id}Get a report by id
Fetch one report by its numeric id. This is the endpoint you poll after submitting an audit. Status moves through pending, scraping, analyzing, and then complete or error. While the audit runs the body carries just the status; once status is complete the full report with the complete signal breakdown is included, and on failure error_message explains why. Returns 404 if no report matches. Read-only, no credit cost.
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | Yes | The report id returned by an audit call. Passed in the path. |
curl https://engagecheck.com/api/v1/audits/142 \
-H "Authorization: Bearer ec_live_xxx"{
"id": 142,
"username": "natgeo",
"status": "complete",
"aqs": 88,
"verdict": "green",
"report": {
"username": "natgeo",
"aqs": 88,
"verdict": "green",
"summary": "@natgeo scored 88 out of 100, indicating a largely authentic audience...",
"signals": [
{
"key": "engagement_rate",
"label": "Engagement Rate",
"score": 92,
"weightPct": 25,
"verdict": "green",
"value": "3.10% ER",
"detail": "Engagement sits inside the healthy 1% to 10% band."
}
],
"commentQuality": {
"bot_comment_percentage": 4,
"pod_activity_detected": false,
"linguistic_summary": "Comments read as genuine conversation."
},
"profile": {
"fullName": "National Geographic",
"followersCount": 280000000,
"followingCount": 142,
"postsCount": 29000,
"isPrivate": false,
"isVerified": true
},
"generatedAt": "2026-06-25T17:00:00.000Z"
},
"error_message": null,
"created_at": "2026-06-25 17:00:00",
"completed_at": "2026-06-25 17:00:42"
}Get the report pushed to you
Pass an optional webhook_url on POST /audits or POST /audits/batch and we POST the result to that URL the moment each audit settles, so you never have to poll. A batch fires one webhook per handle as it finishes. Delivery is best-effort: a webhook that fails or times out never affects the audit or your credits. Only https URLs are accepted, with http allowed for local testing.
On success we send an audit.completed event carrying the full report. On failure we send an audit.failed event with the error message.
{
"event": "audit.completed",
"id": 142,
"username": "natgeo",
"aqs": 88,
"verdict": "green",
"report": {
"username": "natgeo",
"aqs": 88,
"verdict": "green",
"summary": "@natgeo scored 88 out of 100, indicating a largely authentic audience...",
"signals": [
{
"key": "engagement_rate",
"label": "Engagement Rate",
"score": 92,
"weightPct": 25,
"verdict": "green",
"value": "3.10% ER",
"detail": "Engagement sits inside the healthy 1% to 10% band."
}
],
"commentQuality": {
"bot_comment_percentage": 4,
"pod_activity_detected": false,
"linguistic_summary": "Comments read as genuine conversation."
},
"profile": {
"fullName": "National Geographic",
"followersCount": 280000000,
"followingCount": 142,
"postsCount": 29000,
"isPrivate": false,
"isVerified": true
},
"generatedAt": "2026-06-25T17:00:00.000Z"
},
"credits_remaining": 249
}{
"event": "audit.failed",
"id": 142,
"username": "thisuserdoesnotexist",
"error": "Account was not found or has been deleted."
}/creditsCheck your balance
Return the credits left on the key, its per-minute rate limit, and the key prefix so you can confirm which key is in use without exposing the secret.
curl https://engagecheck.com/api/v1/credits \
-H "Authorization: Bearer ec_live_xxx"{
"credits_remaining": 247,
"rate_limit_per_min": 60,
"key_prefix": "ec_live_8Fq2Zx"
}Per-key rate limiting
Each key has a sliding one-minute window, defaulting to 60 requests per minute. Every response carries the current state in its headers, so you can pace your worker without guessing. When you exceed the window the API returns 429 with a Retry-After header that tells you how many seconds to wait.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1782493260
Retry-After: 12 (sent only on a 429)Error codes
Errors always come back as JSON in the shape { "error": "message" } with the matching HTTP status code.
| Status | Name | When it happens |
|---|---|---|
| 202 | Accepted | The audit was queued. POST returns immediately with a report id and poll_url; the result arrives by polling or webhook. |
| 400 | Bad Request | Missing or malformed input, like an empty username or a batch over 50 handles. |
| 401 | Unauthorized | The API key is missing, invalid, or revoked. |
| 402 | Payment Required | The key has no credits left to cover the request. |
| 404 | Not Found | No report exists for the requested id. |
| 429 | Too Many Requests | The per-key rate limit was exceeded. Check the Retry-After header. |
| 502 | Bad Gateway | Only seen on GET /audits/{id} as a transient gateway issue. POST no longer returns 502: a failed scrape, or a private or deleted account, surfaces as the report's error status or an audit.failed webhook. |
The rules, in plain English
Using the API means agreeing to these rules on top of our Terms of Service. They exist to keep the API fast and fair for everyone.
Each key draws from your account's credit balance and you are responsible for every request made with it. Share keys inside your team, not outside your organization, and never ship one in client-side code.
Build the scores and reports into your own workflow, tools, and client deliverables. Don't resell or republish raw audit data as a standalone dataset, feed, or competing audit product.
Don't use the API as a proxy to harvest Instagram profile data at scale or to assemble third-party datasets for redistribution. The product is the audit, not a scraping layer.
Every key is rate limited (60 requests per minute by default). If you need more headroom, contact us and we'll raise it - don't work around the limiter by rotating keys.
We may suspend keys that break these rules. Where possible we email you first, and unused credits stay on your account.
Ready to audit at scale?
Every account gets API access. Create a free account, generate a key in your dashboard, and start auditing in minutes. Buy credits whenever you need more.