The official Python SDK for the Renidly B2B professional data APIs (Data, Live, Email, Account).
Project description
Renidly Python SDK
The official Python SDK for the Renidly B2B professional data APIs — resolve, search, enrich, and verify professional identities (people, organizations, institutions, skills, professional activity, job opportunities, and business email) through one clean, typed client.
from renidly import Renidly
renidly = Renidly("rnd-...")
person = renidly.data.people.retrieve(handle="ryanroslansky")
company = renidly.data.companies.retrieve(slug="stripe")
email = renidly.emails.verify("sundar@google.com")
print(person.headline, company.name, email.deliverable)
- One client, four products —
data,live,emails,account, all off the same key. - Fully typed — method names, parameters, and returns autocomplete in your IDE (ships
py.typed). - Batteries included — automatic retries, transparent pagination, batch jobs, a typed error hierarchy, and an optional self-tuning rate limiter.
- Sync and async — identical surface; just
await.
Table of contents
- Install
- Quickstart
- Authentication
- The four products
- Pagination
- Batch jobs
- Errors
- Configuration
- Automatic rate limiting
- Async
- Response objects
- Advanced
- Requirements & support
Install
pip install renidly
Requires Python 3.9+.
Quickstart
from renidly import Renidly
renidly = Renidly("rnd-...") # or Renidly() and set RENIDLY_API_KEY
# Retrieve a single record (returns None if nothing resolved)
person = renidly.data.people.retrieve(id="prsn_...")
if person:
print(person.first_name, person.headline)
# Search with any filters — they autocomplete in your editor
for p in renidly.data.people.search(title="cto", current_only=True).auto_paging_iter():
print(p.headline)
# Verify an email
v = renidly.emails.verify("sundar@google.com")
print(v.deliverable, v.reason)
That's the whole feel: renidly.<product>.<resource>.<action>(...).
Authentication
Pass your key positionally, via config, or through the environment — whichever you prefer.
Renidly("rnd-...") # positional
Renidly() # reads RENIDLY_API_KEY
Renidly(config=RenidlyConfig(api_key="rnd-...")) # inside the config object
Per-request override (e.g. multi-tenant apps):
renidly.data.people.search(title="cto", options={"api_key": "rnd-tenant-key"})
Grab your key from Workspace → API Keys.
The four products
data — clean, queryable records
Deduplicated professional records addressable by stable opaque IDs (prsn_, org_, inst_, skl_) or rich filters.
# People
renidly.data.people.retrieve(id="prsn_...") # or handle="..."
renidly.data.people.search(title="cto", skills="python", geo_country_code="US")
renidly.data.people.enrich_batch(handles=[...], ids=[...], live=True) # bulk (see Batch jobs)
# Companies
renidly.data.companies.retrieve(slug="google") # or id="org_..."
renidly.data.companies.search(name="stripe", staff_count_min=100)
renidly.data.companies.employees("google", title="engineer", current_only=True)
renidly.data.companies.enrich_batch(ids=[...]) # bulk (see Batch jobs)
# Institutions
renidly.data.institutions.retrieve("stanford") # by normalized name
renidly.data.institutions.search("stanford")
renidly.data.institutions.alumni("stanford", degree="MBA")
# Skills
renidly.data.skills.retrieve("skl_...")
renidly.data.skills.search("python")
# Job changes — trigger-based prospecting
renidly.data.job_changes.search(event_type="joined", days_ago=30)
live — freshest snapshot on demand
Resolve a single subject or run a discovery search.
# People — resolve a public handle to a stable id once, then reuse it
eid = renidly.live.people.resolve_handle("williamhgates").entityId
renidly.live.people.enrich(eid) # or handle="..."
renidly.live.people.employment_history(eid)
renidly.live.people.endorsements(eid)
renidly.live.people.lookalikes(eid)
renidly.live.people.interests(eid)
# Organizations
oid = renidly.live.organizations.resolve_slug("google").id
renidly.live.organizations.enrich(oid)
renidly.live.organizations.headcount(oid)
renidly.live.organizations.similar(oid)
renidly.live.organizations.affiliated(oid)
renidly.live.organizations.activities(oid)
renidly.live.organizations.opportunities("1441,1035") # comma-separated org ids
# Opportunities (job postings)
renidly.live.opportunities.retrieve("4019200001")
renidly.live.opportunities.similar("4019200001")
renidly.live.opportunities.related_views("4019200001")
renidly.live.opportunities.hiring_team("4019200001")
renidly.live.opportunities.by_person(eid)
# Activity
renidly.live.activities.feed(eid)
renidly.live.activities.retrieve(activity_id)
renidly.live.activities.reactions(activity_id)
renidly.live.activities.replies(activity_id, sort_by="date_posted")
renidly.live.activities.replies_by_author(eid)
# Discover (search)
renidly.live.discover.people(keyword="cto", count=25)
renidly.live.discover.organizations(keyword="fintech", headcountRange="51-200")
renidly.live.discover.opportunities(keyword="python", workplaceTypes="remote")
emails — verify, find, and resolve
renidly.emails.verify("sundar@google.com")
renidly.emails.find(first_name="Patrick", last_name="Collison", domain="stripe.com")
renidly.emails.find_by_url("https://example.com/in/someone") # from a professional profile URL
renidly.emails.reverse("john@acme.com") # who is behind this business email
renidly.emails.prospects("acme.com", kind="verified_only") # known contacts for a domain
# Bulk (see Batch jobs)
renidly.emails.verify_batch(["a@x.com", "b@y.com"])
renidly.emails.find_batch([{"first_name": "A", "last_name": "B", "domain": "acme.com"}])
account — balance, tier, and pricing
renidly.account.balance().balance
renidly.account.tier().current_tier.limit_per_minute
renidly.account.enterprise_balance() # for an Enterprise workspace
renidly.account.tiers() # public tier ladder (no key needed)
renidly.account.route_costs() # per-endpoint credit costs (no key needed)
Pagination
Every search/list method returns a list you can use directly and page through transparently.
page = renidly.data.people.search(title="cto", limit=25)
len(page) # items on this page
page[0] # index it
for p in page: ... # iterate this page
page.has_more # is there more?
# ...or walk EVERY page lazily (fetches as it goes, one page in memory at a time)
for person in renidly.data.people.search(title="cto").auto_paging_iter():
print(person.headline)
Batch jobs
Process up to 1000 items in one async job. Submit returns a handle instantly.
job = renidly.data.people.enrich_batch(handles=["ryanroslansky", "williamhgates"], live=True)
# block until done and collect everything
result = job.wait(on_progress=lambda n: print("resolved", n))
print(result.status, result.resolved, "/", result.total)
for row in result.results:
print(row.matched_input, "->", row.headline)
print("not found:", result.not_found)
# ...or stream results as they resolve
for row in renidly.emails.verify_batch(["a@x.com", "b@y.com"]).stream():
print(row.email, row.deliverable)
Available on data.people.enrich_batch, data.companies.enrich_batch, emails.verify_batch, emails.find_batch.
Errors
Every failure raises a specific subclass of RenidlyError, and the message tells you exactly what went wrong.
from renidly import (
RenidlyError, AuthenticationError, InvalidRequestError,
InsufficientCreditsError, NotFoundError, RateLimitError,
PermissionDeniedError, ServiceUnavailableError,
)
try:
renidly.emails.find(first_name="A", last_name="B", domain="bad")
except InvalidRequestError as e:
print(e.message) # "Validation failed"
print(e.field_errors) # {"domain": "must be a bare hostname"}
except RateLimitError as e:
print(e.tier, e.limit, e.retry_after)
except InsufficientCreditsError:
...
except RenidlyError as e: # catch-all
print(e.status_code, e.error_code, e.message, e.errors)
The string form includes the detail, so an uncaught error is self-explanatory:
InvalidRequestError: Validation failed — domain: must be a bare hostname (VALIDATION_ERROR, HTTP 400)
| Exception | When |
|---|---|
AuthenticationError |
missing / invalid key |
PermissionDeniedError |
key valid but not allowed here |
InvalidRequestError |
bad input (see .field_errors) |
InsufficientCreditsError |
not enough credits |
NotFoundError |
job not found / expired |
RateLimitError |
per-minute limit hit (.tier, .limit, .retry_after) |
ServiceUnavailableError |
temporary — retry shortly |
APIConnectionError |
network / timeout |
Not-found lookups: a single
retrieve(...)that resolves nothing returnsNoneby default (not an exception). Setraise_on_not_found=Trueto raise instead.
Configuration
All options live on one object, RenidlyConfig:
from renidly import Renidly, RenidlyConfig
renidly = Renidly("rnd-...", config=RenidlyConfig(
timeout=30,
max_retries=3, # auto-retry on 429 / 503 / connection errors (backoff + jitter)
unwrap_data_obj=True, # return the data model (False -> the full envelope)
raise_on_not_found=False, # single lookups return None when empty (True -> raise)
raise_on_api_error=True, # map failures to typed exceptions
auto_rate_limit=False, # see below
))
| Option | Default | Meaning |
|---|---|---|
api_key |
RENIDLY_API_KEY env |
Your key (positional arg overrides this). |
timeout |
30 |
Per-request timeout (seconds). |
max_retries |
2 |
Retries on transient failures. |
backoff_factor |
0.5 |
Base seconds for exponential backoff. |
base_url |
https://renidly.com |
Override the API host. |
proxy |
None |
HTTP(S) proxy URL. |
default_headers |
{} |
Extra headers on every request. |
unwrap_data_obj |
True |
Return data vs the full envelope. |
raise_on_not_found |
False |
None vs NotFoundError on empty lookups. |
raise_on_api_error |
True |
Raise vs return None on API errors. |
auto_rate_limit |
False |
Self-throttle to your tier's limit. |
rate_limit_per_minute |
None |
Fixed limit (required for enterprise keys). |
rate_limit_safety |
1.0 |
Fraction of the limit to target (e.g. 0.9). |
Automatic rate limiting
Turn it on and the SDK keeps you under your per-minute limit automatically — no limiter to build.
# Regular key: the limit is read from your tier and refreshed automatically.
Renidly("rnd-...", config=RenidlyConfig(auto_rate_limit=True))
# Enterprise key: the limit is fixed — supply it.
Renidly("enterprise-...", config=RenidlyConfig(auto_rate_limit=True, rate_limit_per_minute=550))
It uses a sliding 60-second window so you never exceed the limit, and re-reads your tier after a 429.
Async
Same surface, same names — just await (and use it as a context manager to auto-close the connection pool).
import asyncio
from renidly import AsyncRenidly
async def main():
async with AsyncRenidly("rnd-...") as renidly:
company = await renidly.data.companies.retrieve(slug="stripe")
print(company.name)
async for p in renidly.data.people.search(title="cto").auto_paging_iter():
print(p.headline)
asyncio.run(main())
Response objects
Responses are dynamic and drill-able — access any field (nested included) as an attribute, no schema classes required.
t = renidly.account.tier()
t.current_tier.name # nested attribute access, arbitrarily deep
t.model_dump() # convert to a plain dict anytime
# HTTP metadata is attached to every object
t.last_response.status_code
t.last_response.request_id
Prefer the raw envelope? Set unwrap_data_obj=False and every call returns APIResponse(success=..., data=..., message=...).
Advanced
Per-request options override config for a single call:
renidly.data.skills.search("python", options={"timeout": 5, "api_key": "rnd-other"})
Escape hatch — call any endpoint directly:
env = renidly.raw_request("GET", "/people/search", service="data", params={"title": "cto"})
print(env.success, env.data)
Bring your own HTTP client (connection pools, custom timeouts, mounts):
import httpx
Renidly("rnd-...", http_client=httpx.Client(limits=httpx.Limits(max_connections=50)))
Requirements & support
Questions or issues? Open one on GitHub.
Contributing
Contributions are welcome and appreciated — bug reports, docs, tests, and features alike. See CONTRIBUTING.md to get set up in a couple of minutes, and please review our Code of Conduct. Found a security issue? See SECURITY.md.
License
MIT © Renidly
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file renidly-0.1.1.tar.gz.
File metadata
- Download URL: renidly-0.1.1.tar.gz
- Upload date:
- Size: 40.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1792a2f2f004e26f6f4f32115410ad789686aacbbf5ca1858418585e093e075
|
|
| MD5 |
a3887381969f1a46d2aace171005925a
|
|
| BLAKE2b-256 |
8ee0a5a105d7f8cf674652e6dc7e679f7c8bc4ac985e1638e3c7510b937ad38b
|
Provenance
The following attestation bundles were made for renidly-0.1.1.tar.gz:
Publisher:
release.yml on renidly/renidly-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
renidly-0.1.1.tar.gz -
Subject digest:
b1792a2f2f004e26f6f4f32115410ad789686aacbbf5ca1858418585e093e075 - Sigstore transparency entry: 2253169721
- Sigstore integration time:
-
Permalink:
renidly/renidly-python@0efe0d5387ac43a33ca02f257b072ca2fbf3b46e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/renidly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0efe0d5387ac43a33ca02f257b072ca2fbf3b46e -
Trigger Event:
push
-
Statement type:
File details
Details for the file renidly-0.1.1-py3-none-any.whl.
File metadata
- Download URL: renidly-0.1.1-py3-none-any.whl
- Upload date:
- Size: 38.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e458aeca3ce6f320a7af05ae9e81e49905daa34ba6473fa696dd11f5fa3175fc
|
|
| MD5 |
840ce8748d6ef8a2c622e490f02c586b
|
|
| BLAKE2b-256 |
b1a0bc1288fa87cb9c393c7652b89f6072694e66cfebc5b1f2da501716469443
|
Provenance
The following attestation bundles were made for renidly-0.1.1-py3-none-any.whl:
Publisher:
release.yml on renidly/renidly-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
renidly-0.1.1-py3-none-any.whl -
Subject digest:
e458aeca3ce6f320a7af05ae9e81e49905daa34ba6473fa696dd11f5fa3175fc - Sigstore transparency entry: 2253170182
- Sigstore integration time:
-
Permalink:
renidly/renidly-python@0efe0d5387ac43a33ca02f257b072ca2fbf3b46e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/renidly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0efe0d5387ac43a33ca02f257b072ca2fbf3b46e -
Trigger Event:
push
-
Statement type: