Find every copy of a video — from your own code.
The Revideo.io API runs the same reverse video search as the dashboard: send a video file or a link, and get back every place it has been re-uploaded — with a verdict and a confidence score for each match. Basic searches check Google; Deep Searches check Google, Bing and Yandex and verify every match up to six ways (thumbnail, 2-second video, audio fingerprint, subtitles, on-screen text and metadata).
https://revideo.io/api/developer/v1JSONBearer API keyPaid plansAPI keys are created in Dashboard › Developers and need an active paid plan. Every search uses your plan's allowance, exactly like a search from the dashboard — the API is not billed separately.
Everything you can do on the website you can do here: searches of any size (large files upload in resumable parts and run in the background), keyword, image and deepfake checks, share links, evidence reports, your Library, monitoring and DMCA takedowns — see What the API covers.
Quick start
- Create a key. Open Dashboard › Developers, name the key and copy it. It starts with
rv_live_and is shown only once. - Start a search. Send a link (or upload a file). The API answers straight away with the search's
id.curl https://revideo.io/api/developer/v1/searches \ -H "Authorization: Bearer rv_live_YOUR_KEY" \ -F "video_url=https://www.youtube.com/watch?v=dQw4w9WgXcQ" \ -F "type=deep" - Poll for the results. Ask for the search every 10–15 seconds until
statusiscompleted(orfailed).curl https://revideo.io/api/developer/v1/searches/9b2f6c1e-4a7d-4e2b-9c3a-2f1d8e7b6a50 \ -H "Authorization: Bearer rv_live_YOUR_KEY"
Authentication
Send your key on every request in the Authorization header:
Authorization: Bearer rv_live_4f9a…
An X-API-Key: rv_live_4f9a… header works too. Requests without a valid key get 401 unauthenticated.
- Keep keys secret. Use them only from your server. Never put a key in a website's JavaScript, a mobile app or a public repository.
- One key per place it runs (production, staging, a script) — so you can revoke one without breaking the others. Up to 5 active keys per account.
- Revoking is instant. A revoked key gets
401from the next request on. - Keys follow your plan. If your paid plan ends, every key returns
403 premium_requireduntil you renew — nothing needs to be re-issued.
Rate limits & quotas
| Limit | Value | When it is reached |
|---|---|---|
| Requests per key | 60 a minute | 429 rate_limited with a Retry-After header (seconds) |
| New searches | 20 a minute | 429 — slow down and retry |
| Deep Searches | your plan's monthly allowance | 429 quota_exceeded until the allowance renews |
| Basic searches | your plan's basic allowance | 429 quota_exceeded |
| Upload size | your plan's limit (see GET /me) | 422 validation_failed |
| Monitor checks | one Deep Search each | a watch pauses when your Deep Searches run out |
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. A search is charged when it is queued; if it fails before it can run, the allowance is given back automatically.
How a search works
Searches take from under a minute to several minutes, so the API is asynchronous: you start a search, then read it until it is finished.
queuedaccepted, waiting for a workerprocessingfinding matches (Google / Bing / Yandex) and checking thumbnailsverifyingDeep Search only — matches are found; the other checks are still running on themcompletedfinal — every check has finishedfailedcould not run — see error; the allowance was refundedResults appear as soon as they exist — you can show verifying results early, knowing verdicts may still move from similar to copy as the checks finish. Poll every 10–15 seconds; there is no benefit to polling faster.
Every video search runs in the background, whatever its size — a 5-second clip and a 200 MB file are handled the same way. For files larger than about 50 MB, upload them in parts first (Chunked uploads) so a dropped connection never costs you the whole upload.
What the API covers
The API offers the same features as the website, with the same plan rules — a module that is locked on your plan in the dashboard answers 403 premium_required here, with the reason. GET /me lists which modules your plan includes.
| Website feature | API | Plan |
|---|---|---|
| Reverse video search (Basic & Deep), upload or link | POST /searches | Every paid plan |
| Large uploads, background processing | /uploads + upload_id | Every paid plan |
| YouTube / TikTok / Instagram video finders | POST /searches with platform, POST /image-searches | Every paid plan |
| Keyword video search | POST /keyword-searches | Every paid plan |
| Deepfake detector | POST /deepfake-checks | Every paid plan |
| Search history, re-run, share link, evidence report | /searches/{id}… | Every paid plan |
| Library | /library | Creator, Agency |
| Monitoring | /monitors | Creator, Agency |
| DMCA notices | /dmca/notices | Creator, Agency (not during the free trial) |
| Authorised accounts, takedown details | /authorised-accounts, /takedown-details | Every paid plan |
Deliberately not in the API: signing in, password, billing, cancelling a plan and deleting the account. An API key can never change who owns the account or what it pays — do those in the dashboard.
POST /searches
Start a search. Send one video — a file, a finished chunked upload, or a link — as multipart/form-data (links and upload ids can also be sent as JSON). It always runs in the background.
| Field | Type | Description |
|---|---|---|
video | file | The video to search for. mp4, mov, avi, mkv, webm, mpeg or mpg, up to your plan's upload size. Best for files under about 50 MB. |
upload_id | string | A finished chunked upload — the way to send large files. Used once, then removed. |
video_url | string (URL) | A link to the video — YouTube, TikTok, Instagram, Facebook, X, Reddit and most sites with a playable video. |
type | deep | basic | Optional, default deep. deep uses a Deep Search from your allowance; basic checks Google only. |
platform | youtube | tiktok | instagram | Optional. Only return matches on that platform — the website's video finder tools. Always runs as a Basic search. |
exclude_urls[] | array of URLs | Optional. Links to leave out of the results (for example your own uploads). |
save_to_library | boolean | Optional. Also keep the video in your Library (Creator and Agency plans). |
Response — 202 Accepted
{
"success": true,
"data": {
"id": "9b2f6c1e-4a7d-4e2b-9c3a-2f1d8e7b6a50",
"object": "search",
"type": "deep",
"status": "queued",
"source": { "kind": "url", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
"created_at": "2026-09-22T10:15:04+00:00",
"updated_at": "2026-09-22T10:15:04+00:00",
"counts": { "matches": 0, "copies": 0 },
"error": null,
"dashboard_url": "https://revideo.io/dashboard/searches/9b2f6c1e-4a7d-4e2b-9c3a-2f1d8e7b6a50",
"verification": { "thumbnail": "running", "visual": "queued", "meta": "queued", "transcript": "queued", "ocr": "queued", "audio": "queued" }
},
"message": "Search queued. Poll GET /searches/9b2f6c1e-… for the status and results."
}
GET /searches/{id}
Read a search: its status, the state of each verification check and every result found so far. Results are ordered with confirmed copies first, strongest match first.
Response — 200 OK
{
"success": true,
"data": {
"id": "9b2f6c1e-4a7d-4e2b-9c3a-2f1d8e7b6a50",
"object": "search",
"type": "deep",
"status": "completed",
"source": { "kind": "url", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
"created_at": "2026-09-22T10:15:04+00:00",
"updated_at": "2026-09-22T10:21:37+00:00",
"counts": { "matches": 14, "copies": 6 },
"error": null,
"dashboard_url": "https://revideo.io/dashboard/searches/9b2f6c1e-4a7d-4e2b-9c3a-2f1d8e7b6a50",
"verification": { "thumbnail": "done", "visual": "done", "meta": "na", "transcript": "done", "ocr": "na", "audio": "done" },
"results": [
{
"url": "https://www.tiktok.com/@reposts.daily/video/7412…",
"title": "You won't believe this 😳",
"platform": "TikTok",
"thumbnail": "https://p16-sign.tiktokcdn.com/…",
"uploader": { "handle": "reposts.daily", "name": "Reposts Daily", "url": "https://www.tiktok.com/@reposts.daily" },
"published_at": "2026-09-03",
"verdict": "copy",
"is_copy": true,
"confidence": 94,
"signals": { "THUMB": "match", "META": "na", "VIS": "match", "TXT": "no", "OCR": "na", "AUD": "match" }
}
]
}
}
GET /searches
Your searches, newest first — including the ones started from the dashboard. Results are not included here; read one search to get them.
| Query | Description |
|---|---|
status | Optional: pending, processing, completed or failed. |
per_page | Optional, 1–100, default 20. |
page | Optional, default 1. |
{
"success": true,
"data": [ { "id": "9b2f6c1e-…", "object": "search", "status": "completed", "…": "…" } ],
"meta": { "current_page": 1, "per_page": 20, "total": 48, "last_page": 3, "has_more": true }
}
DELETE /searches/{id}
Delete a finished search and its results. A search that is still running returns 409 unsupported_operation — delete it once it has finished.
{ "success": true, "data": { "deleted": true, "id": "9b2f6c1e-…" } }
POST /searches/{id}/rerun
Run a search again on the same video — the dashboard's Retry / Run again.
- A
failedsearch is retried on the same id, for free — nothing is charged. - A finished search starts a new search (new id) on the same input, charged like any search.
- An uploaded file can be re-run while we still hold it (about 3 days); after that, upload it again. A still-running search returns
409.
Response — 202 Accepted, the Search object (queued)
GET /searches/{id}/evidence-report
The print-ready evidence report for a completed search — every match with its verification scores, timestamps and a pre-drafted takedown notice. Returned as HTML (text/html); open it in a browser and save it as PDF, or render it to PDF with any headless browser.
| Query | Description |
|---|---|
link | Optional. One result's url — the report covers only that source. |
curl "https://revideo.io/api/developer/v1/searches/SEARCH_ID/evidence-report" \
-H "Authorization: Bearer $REVIDEO_API_KEY" -o evidence.html
A search that has not completed returns 409.
GET /me
Your plan and what is left of your allowance — useful before a batch of searches.
{
"success": true,
"data": {
"account": { "name": "Sara Khan", "email": "sara@example.com", "workspace": "creator" },
"plan": { "name": "Creator", "status": "active", "renews_at": "2026-10-14T09:02:11+00:00", "ends_at": null },
"allowance": {
"deep_searches": { "included": 30, "used": 12, "remaining": 18 },
"basic_searches": { "included": null, "used": 40, "per": "month" },
"max_upload_mb": 200
},
"modules": { "deep_search": true, "library": true, "monitoring": true, "dmca": true },
"api_key": { "name": "Production server", "prefix": "rv_live_4f9a" }
}
}
included: null means unlimited. modules says which parts of the API your plan opens.
Chunked uploads (large files)
Send big videos in parts of 10 MB. Each part is a small request, a failed part is simply sent again, and an interrupted upload resumes where it stopped. Once complete, pass its id as upload_id to POST /searches, POST /library or POST /monitors. The size limit is your plan's upload size (max_upload_mb in GET /me). Unused uploads expire after 24 hours.
| Request | What it does |
|---|---|
POST /uploads | Start an upload. JSON {"filename":"clip.mp4","size_bytes":157286400} → the upload with parts_total and part_size_bytes. |
PUT /uploads/{id}/parts/{n} | Send part n (1, 2, 3 …) as the raw request body. Every part is exactly part_size_bytes, except the last. Parts can go in any order, or in parallel. |
GET /uploads/{id} | Which parts have arrived (parts_received) — to resume after a dropped connection. |
POST /uploads/{id}/complete | Join the parts. Answers status: "complete", or 422 naming the missing parts. |
DEL /uploads/{id} | Throw an upload away. |
{
"success": true,
"data": {
"id": "upl_3kq9x0m2b7c4d1e8f5g6h7j2",
"object": "upload",
"status": "open",
"filename": "clip.mp4",
"size_bytes": 157286400,
"part_size_bytes": 10485760,
"parts_total": 15,
"parts_received": [],
"created_at": "2026-09-22T10:15:04+00:00",
"expires_at": "2026-09-23T10:15:04+00:00"
}
}
Python — upload a large file in parts, then search it
import os, requests
BASE = "https://revideo.io/api/developer/v1"
H = {"Authorization": f"Bearer {os.environ['REVIDEO_API_KEY']}"}
path = "long-video.mp4"
up = requests.post(f"{BASE}/uploads", headers=H,
json={"filename": os.path.basename(path), "size_bytes": os.path.getsize(path)}).json()["data"]
with open(path, "rb") as f:
for n in range(1, up["parts_total"] + 1):
chunk = f.read(up["part_size_bytes"])
for attempt in range(3): # retry a failed part, not the whole file
r = requests.put(f"{BASE}/uploads/{up['id']}/parts/{n}", headers=H, data=chunk)
if r.ok: break
requests.post(f"{BASE}/uploads/{up['id']}/complete", headers=H).raise_for_status()
search = requests.post(f"{BASE}/searches", headers=H, json={"upload_id": up["id"], "type": "deep"}).json()["data"]
print(search["id"], search["status"]) # queued — poll GET /searches/{id}
POST /keyword-searches
Find videos by words — the website's keyword video search. Answers in the same request (nothing to poll) and uses one Basic search.
| Field | Type | Description |
|---|---|---|
keyword | string | Required, 3–300 characters. |
platform | string | Optional: all (default), youtube, tiktok, vimeo, facebook, twitter, instagram. |
page | 1–10 | Optional. The next page of results ("load more"). |
exclude_urls[] | array | Optional. Links already shown, to leave out. |
{ "success": true, "data": { "object": "keyword_search", "keyword": "cat video", "platform": "youtube", "page": 1, "results": [ { "url": "…", "title": "…", "…": "…" } ] } }
results are Result objects.
POST /image-searches
Search one platform with a single frame — the finder tools' "Search by frame". Answers in the same request and uses one Basic search.
| Field | Type | Description |
|---|---|---|
image | file | Required. jpg, png, webp or gif, up to 10 MB. |
platform | string | Required: youtube, tiktok or instagram. |
POST /deepfake-checks
Is this video or image AI-generated or manipulated? The website's deepfake detector. Send a video file, an image file, or a video_url. Answers in the same request and uses one Basic search.
{ "success": true, "data": { "object": "deepfake_check", "input": "url", "verdict": "real", "confidence": 0.93, "result": "authentic", "details": { }, "meta": { } } }
Library
Your own videos, kept so you can search and monitor them at any time without uploading again — the dashboard's Library. Creator and Agency plans.
| Request | What it does |
|---|---|
GET /library | List videos. Query: filter (all, copies, watched, unwatched, clean, unsearched), sort (recent, copies, name, oldest), q, per_page, page. |
POST /library | Add videos: video or videos[] (files, up to 20), upload_id, video_url or video_urls[] (up to 50). Answers added and skipped (with a reason each — e.g. already in your Library). |
POST /library/from-search/{search_id} | Keep a search's video in the Library. |
GET /library/{id} | One video, with every search run on it (searches). |
GET /library/{id}/file | Download the stored video file. |
POST /library/{id}/search | Search it again: {"type":"deep"} or basic. Answers 202 with the new Search. |
DEL /library/{id} | Remove it. Searches already run stay; a monitor keeps its own copy and keeps watching. |
{
"id": "4c1d…", "object": "library_video", "title": "launch-teaser.mp4", "kind": "file",
"source_url": null, "file_url": "https://revideo.io/api/developer/v1/library/4c1d…/file", "poster_url": "https://…",
"duration_seconds": 42, "size_bytes": 18350112, "added_from": "api", "created_at": "2026-09-22T10:15:04+00:00",
"latest_search": { "id": "9b2f…", "type": "deep", "status": "completed", "copies": 6, "matches": 14 },
"searches_count": 3,
"monitor": { "id": "7e0a…", "active": true }
}
Monitoring
Watch a video: we re-run a full Deep Search on a schedule and record every new copy — the dashboard's Monitoring. Each check uses one Deep Search from your allowance; the first check runs straight away. Creator and Agency plans.
| Request | What it does |
|---|---|
GET /monitors | List watches. Query: active (true / false), per_page, page. |
POST /monitors | Start watching one of: search_id (a finished search's video), library_id, video_url, a video file or an upload_id. Optional frequency and save_to_library. Answers 201, or 200 when that video is already watched. |
GET /monitors/{id} | One watch with its check history (checks) and the copies found last time (last_matches). |
PATCH /monitors/{id} | Change frequency, or pause / resume with active. Resuming needs Deep Searches left. |
POST /monitors/{id}/check | Check now (at most every 30 minutes; one at a time). |
DEL /monitors/{id} | Stop watching. |
frequency: daily (about 30 Deep Searches a month), 2day — the default (about 15), or weekly (about 4).
{
"id": "7e0a…", "object": "monitor", "label": "youtube.com/watch?v=dQw4w9WgXcQ", "active": true,
"frequency": "2day", "frequency_label": "Every 2 days",
"source": { "kind": "url", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
"library_id": null, "from_search_id": null, "thumbnail": "https://…",
"check_running": false, "last_checked_at": "2026-09-22T02:10:00+00:00", "next_check_at": "2026-09-24T02:10:00+00:00",
"matches_found_total": 9, "known_links_count": 31, "last_error": null, "latest_search_id": "b81c…",
"created_at": "2026-09-01T08:00:00+00:00",
"checks": [ { "at": "2026-09-22T02:10:00+00:00", "found": 2, "outcome": "ran" } ]
}
DMCA notices
Takedowns from draft to outcome — the dashboard's DMCA module. We draft each notice from the verified evidence (only confirmed copies — never a look-alike) and track what the platform does; you send it, because the sworn statements are yours. Creator and Agency plans, not during the free trial.
| Request | What it does |
|---|---|
GET /dmca/notices | List notices. Query: filter — all, draft, sent (waiting on the platform), removed, problem (needs you). |
POST /dmca/notices | Draft notices: search_id plus links[] (result URLs) or "all": true for every confirmed copy. Optional monitor_id. |
GET /dmca/notices/{id} | The notice: full notice_text, where and how to send it (send_to), status and history. |
POST …/{id}/sent | Record that you sent it: via (portal, email, other), optional reference and sent_on, and "affirm": true (you confirm the statements are true). |
POST …/{id}/response | What the platform said: outcome — acknowledged, removed, rejected or counter; optional reference, note, counter_received_on, counter_statement. |
POST …/{id}/decision | After a counter-notice: decision — withdraw, evidence or court — with "confirm": true. |
POST …/{id}/check-link | Is the copy still online? Moves the notice to removed — or to restored when a removed copy is back. |
PATCH …/{id}/notes | Your private notes. |
POST …/{id}/withdraw | Withdraw it (a draft is deleted). |
Statuses: draft → sent → acknowledged → removed; or rejected, counter (a counter-notice — counter_notice.business_days_left counts down the 10 business days you have), restored (back online after removal), withdrawn, closed. needs_you is true when the next move is yours.
A notice legally needs your postal address and phone number — set them with PATCH /takedown-details first, or …/sent answers 409.
Settings
| Request | What it does |
|---|---|
GET /authorised-accounts | Accounts you authorise (your other channels, partners). Their uploads show as verdict: "authorised" — never counted as copies, never alerted on. |
POST /authorised-accounts | Add one: handle (@name or a profile link), optional reason. Up to 100. |
DEL /authorised-accounts/{id} | Remove one — their copies are flagged again from the next search. |
GET /takedown-details | The sender block every DMCA notice is built from, and complete. |
PATCH /takedown-details | Set from, email, phone, address, company, capacity (owner or agent), profile. |
The Search object
| Field | Type | Description |
|---|---|---|
id | string | The search's id (UUID). |
type | deep | basic | What kind of search ran. |
status | string | queued, processing, verifying, completed or failed — see How a search works. |
source | object | {"kind":"url","url":…} or {"kind":"file","filename":…}. |
counts | object | matches — results found; copies — how many are confirmed copies. |
verification | object | Deep Search only. Each check: queued, running, done, na (could not run for this video), failed or timed out. Checks: thumbnail, visual (2-second video), meta, transcript (subtitles), ocr (on-screen text), audio (fingerprint). |
results | array | Only on GET /searches/{id}. See The Result object. |
error | string | null | Why a failed search failed. |
platform | string | null | Set when the search was limited to one platform (youtube, tiktok, instagram). |
library_id | string | null | The Library video this search belongs to, if any. |
share_url | string | null | The live public link, when sharing is on (share). |
dashboard_url | string | The same search in your dashboard. |
created_at, updated_at | ISO 8601 | Timestamps (UTC). |
The Result object
| Field | Type | Description |
|---|---|---|
url | string | Where the match was found. |
title, platform, thumbnail | string | null | As published on that site. |
uploader | object | null | handle, name, url of the account that posted it, when known. |
published_at | string | null | When that copy was published, when known. |
match_at_seconds | number | null | Where your footage starts inside their video, in seconds — from the video or audio check. null when nothing timed it (a Basic search, or a title-only match). |
url_at_match | string | null | The same url, opening at match_at_seconds. Only YouTube, Vimeo, Dailymotion, Twitch and Facebook take a start time; elsewhere it is the plain link. |
verdict | string | copy — your video; similar — looks alike but not proven; same-person — the same person in a different video; authorised — one of your own accounts (set in Settings). |
is_copy | boolean | Shortcut for verdict == "copy". |
confidence | 0–100 | null | How sure the checks are, weighted by what could run. 90+ means two physical proofs (e.g. video and audio both matched). |
signals | object | Each check for this match: match, no or na (could not run). Keys: THUMB, META, VIS, TXT, OCR, AUD. |
Errors
Errors use normal HTTP status codes and always the same body. Branch on error_code, not on the message — messages are for people and may change.
{
"success": false,
"data": null,
"message": "Send one video: a file (field \"video\"), a finished chunked upload (\"upload_id\") or a link (\"video_url\").",
"error_code": "validation_failed",
"errors": { "video_url": ["Send one video: a file (field \"video\"), a finished chunked upload (\"upload_id\") or a link (\"video_url\")."] }
}
| HTTP | error_code | Meaning |
|---|---|---|
| 401 | unauthenticated | Missing, invalid or revoked API key. |
| 403 | premium_required | The key's account has no active paid plan — or the module (Library, Monitoring, DMCA) is not on your plan; the message says which. |
| 403 | account_inactive | The account is disabled. |
| 404 | not_found | Nothing with that id on this account. |
| 409 | unsupported_operation | Not possible in the current state — a search still running, a report for an unfinished search, a notice already sent, a paused monitor. |
| 422 | validation_failed | A field is missing or wrong — see errors. |
| 429 | rate_limited | Too many requests — wait Retry-After seconds. |
| 429 | quota_exceeded | Your plan's allowance for this type of search is used up. |
| 503 | upstream_unavailable | The search engine is busy (instant tools) — retry in a minute. |
| 500 | server_error | Something went wrong on our side — safe to retry after a moment. |
Full examples
Start a Deep Search for a link, wait for it to finish, print every confirmed copy.
# 1. start (a link — or use -F "video=@@/path/to/clip.mp4" to upload a file)
curl https://revideo.io/api/developer/v1/searches \
-H "Authorization: Bearer $REVIDEO_API_KEY" \
-F "video_url=https://www.tiktok.com/@me/video/7412…" \
-F "type=deep"
# 2. poll until status is "completed" or "failed"
curl https://revideo.io/api/developer/v1/searches/SEARCH_ID \
-H "Authorization: Bearer $REVIDEO_API_KEY"
// Node 18+ (server side only — never ship your key to a browser)
const BASE = "https://revideo.io/api/developer/v1";
const headers = { Authorization: `Bearer ${process.env.REVIDEO_API_KEY}` };
const form = new FormData();
form.append("video_url", "https://www.tiktok.com/@me/video/7412…");
form.append("type", "deep");
let res = await fetch(`${BASE}/searches`, { method: "POST", headers, body: form });
let { data: search } = await res.json();
while (!["completed", "failed"].includes(search.status)) {
await new Promise(r => setTimeout(r, 15000));
({ data: search } = await (await fetch(`${BASE}/searches/${search.id}`, { headers })).json());
}
for (const r of search.results ?? []) {
if (r.is_copy) console.log(`${r.confidence}% ${r.platform} ${r.url}`);
}
import os, time, requests
BASE = "https://revideo.io/api/developer/v1"
H = {"Authorization": f"Bearer {os.environ['REVIDEO_API_KEY']}"}
# a link; to upload a file instead: files={"video": open("clip.mp4", "rb")}
search = requests.post(f"{BASE}/searches", headers=H,
data={"video_url": "https://www.tiktok.com/@me/video/7412…", "type": "deep"}).json()["data"]
while search["status"] not in ("completed", "failed"):
time.sleep(15)
search = requests.get(f"{BASE}/searches/{search['id']}", headers=H).json()["data"]
for r in search.get("results", []):
if r["is_copy"]:
print(f"{r['confidence']}% {r['platform']} {r['url']}")
<?php
$base = 'https://revideo.io/api/developer/v1';
$key = getenv('REVIDEO_API_KEY');
$call = function (string $method, string $path, array $fields = []) use ($base, $key) {
$ch = curl_init($base . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", 'Accept: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $fields ?: null, // a file: ['video' => new CURLFile('clip.mp4')]
]);
return json_decode(curl_exec($ch), true)['data'];
};
$search = $call('POST', '/searches', ['video_url' => 'https://www.tiktok.com/@me/video/7412…', 'type' => 'deep']);
while (! in_array($search['status'], ['completed', 'failed'], true)) {
sleep(15);
$search = $call('GET', '/searches/' . $search['id']);
}
foreach ($search['results'] ?? [] as $r) {
if ($r['is_copy']) echo "{$r['confidence']}% {$r['platform']} {$r['url']}\n";
}
Best practices
- Poll gently — every 10–15 seconds, and stop at
completedorfailed. - Use Basic for quick checks and Deep when you need proof — Deep uses your Deep Search allowance.
- Handle
429by waitingRetry-Afterseconds before retrying. - Use
is_copyfor decisions andconfidencefor ranking;signalstells you which checks matched. - Upload large files in parts (chunked uploads) — retry the part that failed, not the whole file.
- Close the loop over the API — watch important videos with monitors, and turn confirmed copies into DMCA notices; the same items appear in the dashboard.