REVIDEO.IO API · v1

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).

Base URLhttps://revideo.io/api/developer/v1
FormatJSON
AuthBearer API key
AccessPaid plans

API 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

  1. Create a key. Open Dashboard › Developers, name the key and copy it. It starts with rv_live_ and is shown only once.
  2. 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"
  3. Poll for the results. Ask for the search every 10–15 seconds until status is completed (or failed).
    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 401 from the next request on.
  • Keys follow your plan. If your paid plan ends, every key returns 403 premium_required until you renew — nothing needs to be re-issued.

Rate limits & quotas

LimitValueWhen it is reached
Requests per key60 a minute429 rate_limited with a Retry-After header (seconds)
New searches20 a minute429 — slow down and retry
Deep Searchesyour plan's monthly allowance429 quota_exceeded until the allowance renews
Basic searchesyour plan's basic allowance429 quota_exceeded
Upload sizeyour plan's limit (see GET /me)422 validation_failed
Monitor checksone Deep Search eacha 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 worker
processingfinding matches (Google / Bing / Yandex) and checking thumbnails
verifyingDeep Search only — matches are found; the other checks are still running on them
completedfinal — every check has finished
failedcould not run — see error; the allowance was refunded

Results 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 featureAPIPlan
Reverse video search (Basic & Deep), upload or linkPOST /searchesEvery paid plan
Large uploads, background processing/uploads + upload_idEvery paid plan
YouTube / TikTok / Instagram video findersPOST /searches with platform, POST /image-searchesEvery paid plan
Keyword video searchPOST /keyword-searchesEvery paid plan
Deepfake detectorPOST /deepfake-checksEvery paid plan
Search history, re-run, share link, evidence report/searches/{id}…Every paid plan
Library/libraryCreator, Agency
Monitoring/monitorsCreator, Agency
DMCA notices/dmca/noticesCreator, Agency (not during the free trial)
Authorised accounts, takedown details/authorised-accounts, /takedown-detailsEvery 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.

GET /searches

Your searches, newest first — including the ones started from the dashboard. Results are not included here; read one search to get them.

QueryDescription
statusOptional: pending, processing, completed or failed.
per_pageOptional, 1–100, default 20.
pageOptional, 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 }
}

POST /searches/{id}/rerun

Run a search again on the same video — the dashboard's Retry / Run again.

  • A failed search 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)

POST /searches/{id}/share

Turn on a public, read-only results page for a search — the same link as the website's Share button. Anyone with the link sees the results only: no account details, no uploaded file, no actions. Calling it again returns the same link.

{ "success": true, "data": { "shared": true, "url": "https://revideo.io/r/Xq3…" } }

DELETE /searches/{id}/share stops sharing — the link stops working at once; sharing again makes a new link. The live link is also on the Search object as share_url.

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.

QueryDescription
linkOptional. 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.

RequestWhat it does
POST /uploadsStart 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}/completeJoin 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.

FieldTypeDescription
keywordstringRequired, 3–300 characters.
platformstringOptional: all (default), youtube, tiktok, vimeo, facebook, twitter, instagram.
page1–10Optional. The next page of results ("load more").
exclude_urls[]arrayOptional. 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.

FieldTypeDescription
imagefileRequired. jpg, png, webp or gif, up to 10 MB.
platformstringRequired: 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.

RequestWhat it does
GET /libraryList videos. Query: filter (all, copies, watched, unwatched, clean, unsearched), sort (recent, copies, name, oldest), q, per_page, page.
POST /libraryAdd 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}/fileDownload the stored video file.
POST /library/{id}/searchSearch 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.

RequestWhat it does
GET /monitorsList watches. Query: active (true / false), per_page, page.
POST /monitorsStart 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}/checkCheck 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.

RequestWhat it does
GET /dmca/noticesList notices. Query: filter — all, draft, sent (waiting on the platform), removed, problem (needs you).
POST /dmca/noticesDraft 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}/sentRecord that you sent it: via (portal, email, other), optional reference and sent_on, and "affirm": true (you confirm the statements are true).
POST …/{id}/responseWhat the platform said: outcome — acknowledged, removed, rejected or counter; optional reference, note, counter_received_on, counter_statement.
POST …/{id}/decisionAfter a counter-notice: decision — withdraw, evidence or court — with "confirm": true.
POST …/{id}/check-linkIs the copy still online? Moves the notice to removed — or to restored when a removed copy is back.
PATCH …/{id}/notesYour private notes.
POST …/{id}/withdrawWithdraw 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

RequestWhat it does
GET /authorised-accountsAccounts you authorise (your other channels, partners). Their uploads show as verdict: "authorised" — never counted as copies, never alerted on.
POST /authorised-accountsAdd 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-detailsThe sender block every DMCA notice is built from, and complete.
PATCH /takedown-detailsSet from, email, phone, address, company, capacity (owner or agent), profile.

The Search object

FieldTypeDescription
idstringThe search's id (UUID).
typedeep | basicWhat kind of search ran.
statusstringqueued, processing, verifying, completed or failed — see How a search works.
sourceobject{"kind":"url","url":…} or {"kind":"file","filename":…}.
countsobjectmatches — results found; copies — how many are confirmed copies.
verificationobjectDeep 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).
resultsarrayOnly on GET /searches/{id}. See The Result object.
errorstring | nullWhy a failed search failed.
platformstring | nullSet when the search was limited to one platform (youtube, tiktok, instagram).
library_idstring | nullThe Library video this search belongs to, if any.
share_urlstring | nullThe live public link, when sharing is on (share).
dashboard_urlstringThe same search in your dashboard.
created_at, updated_atISO 8601Timestamps (UTC).

The Result object

FieldTypeDescription
urlstringWhere the match was found.
title, platform, thumbnailstring | nullAs published on that site.
uploaderobject | nullhandle, name, url of the account that posted it, when known.
published_atstring | nullWhen that copy was published, when known.
match_at_secondsnumber | nullWhere 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_matchstring | nullThe same url, opening at match_at_seconds. Only YouTube, Vimeo, Dailymotion, Twitch and Facebook take a start time; elsewhere it is the plain link.
verdictstringcopy — 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_copybooleanShortcut for verdict == "copy".
confidence0–100 | nullHow sure the checks are, weighted by what could run. 90+ means two physical proofs (e.g. video and audio both matched).
signalsobjectEach 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\")."] }
}
HTTPerror_codeMeaning
401unauthenticatedMissing, invalid or revoked API key.
403premium_requiredThe key's account has no active paid plan — or the module (Library, Monitoring, DMCA) is not on your plan; the message says which.
403account_inactiveThe account is disabled.
404not_foundNothing with that id on this account.
409unsupported_operationNot possible in the current state — a search still running, a report for an unfinished search, a notice already sent, a paused monitor.
422validation_failedA field is missing or wrong — see errors.
429rate_limitedToo many requests — wait Retry-After seconds.
429quota_exceededYour plan's allowance for this type of search is used up.
503upstream_unavailableThe search engine is busy (instant tools) — retry in a minute.
500server_errorSomething 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"

Best practices

  • Poll gently — every 10–15 seconds, and stop at completed or failed.
  • Use Basic for quick checks and Deep when you need proof — Deep uses your Deep Search allowance.
  • Handle 429 by waiting Retry-After seconds before retrying.
  • Use is_copy for decisions and confidence for ranking; signals tells 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.
Ready to build?Create your API key in the dashboard.
Get an API key →