Python SDK
Official osintverse package for the OSINTOne API — install, auth, create, wait, bulk, unlock, premium, and errors.
Official client for the OSINTOne API on osintverse.com. Same providers, prices, and prepaid wallet as the SearchIn UI.
pip install osintverseRequires Python 3.10+. Package: osintverse on PyPI. Source: osintverse-pythonsdk.
It talks to https://apiv1.osintverse.com. It is not affiliated with other products that reuse the OSINTverse name.
OSINTOne API only
This SDK covers SearchIn lookups (create, poll, bulk, LeakRadar unlock, OSINT Industries premium). GraphIn — seed, canvas, path, share — stays in the GraphIn UI. Create keys in Dashboard → API keys; there is no key-management API.
Authenticate
Sign in once from a terminal. It shows a code, you approve it at /device while signed in, and a new key is saved to the config file the JS CLI also reads (%APPDATA%\osintverse on Windows, ~/.config/osintverse elsewhere), so npx osintverse login works for both:
python -m osintverse login # --json for agents, --no-browser on servers
python -m osintverse whoami
python -m osintverse logout # forgets the saved key; revoke it in the dashboardOr create a key (prefix ov_) and copy it once:
export OSINTVERSE_API_KEY=ov_your_api_keyfrom osintverse import OSINTverse
client = OSINTverse() # api_key="ov_…", else OSINTVERSE_API_KEY, else the saved keydevice_login() runs the same sign-in from code. Requires osintverse 0.2.0 or later.
Close the HTTP client when you are done, or use a context manager:
with OSINTverse() as client:
search = client.search.create(
provider="leakradar",
input_type="email",
query="user@example.com",
wait=True,
)client.health() and client.providers.list() work without a key. Search methods require one. Optional: OSINTverse(base_url="https://apiv1.osintverse.com", timeout=60).
To integrate against sample data, point base_url at the API sandbox and pass api_key="ov_sandbox". Those routes return fixed results and do not bill.
First search
search = client.search.create(
provider="leakradar",
input_type="email",
query="user@example.com",
wait=True,
)
print(search.id, search.status, search.cost_usd, search.balance_after)
if search.result:
for match in search.result.matches:
print(match)Quote first when cost matters. quote() never charges:
quote = client.search.quote(provider="leakradar", input_type="email", count=2)
if not quote.sufficient_balance:
print(f"Add ${quote.shortfall_usd} at {quote.top_up_url}")Every create sends an Idempotency-Key: a fresh UUID unless you pass idempotency_key=. Pass your own and reuse it when you retry after a timeout, and the API returns the original search instead of charging again.
Pick a valid provider + input_type from client.providers.list() or the pricing matrix. Invalid pairs raise ValidationError (HTTP 422).
Most providers finish in the create response. FaceCheck image jobs, Hunter Email Verifier, and OSINT Industries lookups can return running — wait=True polls every ~2 seconds (default timeout 120s). OSINT Industries result.matches grows while the job is still running.
Hunter.io name queries must be Full Name example.com (person name, then company domain).
Methods
| SDK | HTTP |
|---|---|
client.health() | GET /health |
client.providers.list() | GET /v1/providers |
client.search.create(...) | POST /v1/search |
client.search.bulk(...) | POST /v1/search/bulk |
client.search.retrieve(id) / .get(id) | GET /v1/search/{id} |
client.search.wait(id) | poll retrieve until terminal |
client.search.unlock(id) | POST /v1/search/{id}/unlock |
client.search.premium(id) | POST /v1/search/{id}/premium |
create / bulk kwargs: premium=False, wait=False, poll_interval=2, timeout=120.
Stop an in-flight OSINT Industries job with POST /v1/search/{id}/cancel (HTTP; not a dedicated SDK method yet). Modules already returned stay on the job and the original charge is kept.
Without wait, inspect search.status and poll yourself:
search = client.search.create(
provider="facecheck",
input_type="image",
query="https://example.com/photo.jpg",
)
if search.running:
search = client.search.wait(search.id)Each poll counts toward the 1,000 requests/day/key limit. See authentication.
Results
A Search object mirrors the API job: id, status, provider, input_type, query, cost_usd, balance_after, error, poll_url, result.
result.matches— normalized rows to consume in a pipeline (shape varies by provider).result.sources— adapter metadata (counts, paging).- Provider field notes: provider guides.
- Status meanings: search workflow.
Bulk
batch = client.search.bulk(
input_type="email",
queries=["alice@example.com", "bob@example.com"],
providers=["leakradar", "dehashed"],
wait=True,
)
print(batch.batch_id, batch.estimated_cost_usd, batch.summary)
for job in batch.searches:
print(job.provider, job.query, job.status)Limits: 50 queries, 100 jobs (queries × providers). Partial success is normal. Bulk wait=True polls every running job and does not raise if a sibling is failed or refunded — read batch.summary. The bulk call counts once toward the daily key quota; each poll that wait=True makes counts once too.
LeakRadar unlocks and OSINT Industries premium
client.search.unlock(search.id) # leftover LeakRadar credentials; $0.50 / 1k
client.search.premium(search.id) # premium modules after a basic OSINT Industries search; +$1.80Or at create: client.search.create(..., premium=True) (OSINT Industries only; ignored on other providers). Insufficient extra balance raises PaymentRequiredError (HTTP 402). Already-unlocked / already-premium jobs return the current search with no extra charge.
Async
from osintverse import AsyncOSINTverse
async with AsyncOSINTverse() as client:
search = await client.search.create(
provider="leakradar",
input_type="email",
query="user@example.com",
wait=True,
)Same method names, all awaited.
Errors
HTTP { "detail": "…" } maps to:
| Status | Exception |
|---|---|
| 401 | AuthenticationError |
| 402 | PaymentRequiredError |
| 403 | ForbiddenError |
| 404 | NotFoundError |
| 409 | ConflictError (idempotency key reused for a different search) |
| 422 | ValidationError |
| 5xx | APIError |
Every APIError has status_code, detail, and, when the API sends them, code (for example insufficient_balance) and hint (what to do next).
A 200 job can still fail. wait=True on create raises:
SearchFailedError— usually insufficient balance (status: failed, not charged)SearchRefundedError— upstream failed after billing (status: refunded)WaitTimeoutError— stillrunningaftertimeout
from osintverse import SearchFailedError, ValidationError
try:
search = client.search.create(
provider="leakradar",
input_type="email",
query="user@example.com",
wait=True,
)
except ValidationError as exc:
print("Bad provider/input_type:", exc)
except SearchFailedError as exc:
print(exc.search.error)Top up at Billing before you automate. Full HTTP table: errors.