intigriti.py
An async, fully typed Python wrapper for the Intigriti researcher API — the API behind a researcher's personal access token, serving programs, their scope and rules of engagement, the program activity feed and payouts.
Which API? This wraps the researcher API only, at
https://api.intigriti.com/external/researcher. Intigriti also runs a separate company API for organisations automating their own programs; it has its own host, authentication and payloads, and is not covered here.
Status: early development — the public API of this library may still change.
What's covered
Every endpoint of researcher API v1:
| Endpoint | Method |
|---|---|
GET /v1/programs |
client.programs.list() · .iterate() |
GET /v1/programs/{programId} |
client.programs.get() |
GET /v1/programs/activities |
client.programs.activities() · .iterate_activities() |
GET /v1/programs/{programId}/domains/{versionId} |
client.programs.domains() |
GET /v1/programs/{programId}/rules-of-engagements/{versionId} |
client.programs.rules_of_engagement() |
GET /v1/payouts (BETA) |
client.payouts.list() · .iterate() |
Requirements
- Python 3.13+
Installation
Not published on PyPI yet — install from source:
uv add git+https://github.com/DonAsako/intigriti.py
# or
pip install git+https://github.com/DonAsako/intigriti.py
Usage
Generate a personal access token from your Intigriti account settings, then:
import asyncio
import os
import intigriti
async def main() -> None:
async with intigriti.Client(os.environ["INTIGRITI_TOKEN"]) as client:
page = await client.programs.list(status=intigriti.ProgramStatus.OPEN, limit=100)
print(f"{page.max_count} programs available")
# Pages are walked for you; the API serves 50 records at a time by default.
async for program in client.programs.iterate(following=True):
print(program.handle, program.max_bounty.value, program.max_bounty.currency)
detail = await client.programs.get(page.records[0].id)
for domain in detail.domains.content or []:
print(domain.endpoint, domain.tier.value)
asyncio.run(main())
The same example, ready to run, lives in
examples/quickstart.py:
INTIGRITI_TOKEN=xxxxx uv run python examples/quickstart.py
Everything is typed: responses are Pydantic models with
snake_case attributes, epoch timestamps decoded to aware datetimes and bounties to
Decimal.
Enumerations
The API returns enumerations as {"id": 3, "value": "Open"}. Only the id is contractual,
so compare against the members of intigriti.enums rather than against the label:
if program.status.id == intigriti.ProgramStatus.OPEN:
...
An id this library does not know is decoded as a plain integer rather than raising, so a new program status will not break a running client.
Errors
Failed requests raise an IntigritiAPIError subclass carrying the status, the API error
code and the identifier to quote when reporting a problem:
try:
await client.programs.get(program_id)
except intigriti.IntigritiRateLimitError as exc:
print("throttled, retry after", exc.retry_after)
except intigriti.IntigritiAPIError as exc:
print(exc.status_code, exc.code, exc.identifier)
Note that Intigriti signals rate limiting with HTTP 403 rather than 429, so
IntigritiRateLimitError inherits from IntigritiPermissionError.
Development
Requires uv.
uv sync # install deps + dev tools
uv run pre-commit install # install git hooks (pre-commit + commit-msg)
With just, just install does both.
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy # type-check
uv run pytest # tests
uv run pytest --cov # tests with coverage
just # list all recipes
just fix # auto-fix lint + format
just check # lint + format-check + typecheck + test (mirrors CI)
Tooling
| Tool | Purpose |
|---|---|
| uv | Dependency & virtualenv management |
| Hatchling | PEP 517 build backend |
| Ruff | Linter + formatter |
| Mypy | Static type checking (strict mode) |
| Pytest | Test runner (+ coverage, asyncio, benchmark, xdist, mock, timeout, dotenv) |
| pre-commit | Git hooks orchestration |
| gitlint | Conventional Commits enforcement |
| GitHub Actions | CI: lint, type-check, tests on every push/PR |
| just | Task runner for common dev commands (optional) |
Layout
.
├── intigriti/ # source package
│ ├── client.py # public entry point
│ ├── enums.py # documented enumeration ids
│ ├── exceptions.py # error hierarchy
│ ├── models/ # typed response models
│ ├── resources/ # endpoint groups (programs, payouts)
│ ├── pagination.py # limit/offset iteration
│ ├── _http.py # async transport
│ └── py.typed # PEP 561 marker (ships type hints to consumers)
├── examples/
│ └── quickstart.py
├── tests/
│ └── unit/
├── .github/
│ ├── workflows/ci.yml # CI pipeline
│ ├── ISSUE_TEMPLATE/
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── CONTRIBUTING.md
│ ├── SECURITY.md
│ └── dependabot.yml
├── .pre-commit-config.yaml
├── .editorconfig
├── .gitlint
├── .yamllint.yaml
├── justfile
├── pyproject.toml # single source of truth (ruff, mypy, pytest, coverage)
└── uv.lock
Contributing
See CONTRIBUTING.md. Commits follow
Conventional Commits and are validated by gitlint.
Disclaimer
Unofficial project, not affiliated with or endorsed by Intigriti.
License
Release files for intigriti 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| intigriti-0.1.0.tar.gz | 21.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| intigriti-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.0 kB
Release files / intigriti-0.1.0.tar.gz
| Download URL | intigriti-0.1.0.tar.gz |
|---|---|
| Size | 21.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5acef4ca99de8b445c1736c34e367d350f233a936b48d06f5bee8583f5e82e9
|
|
BLAKE2b-256 checksum How to use checksums |
75419dae282ee90b9fd2273a406d27392508928e791889ca32f2521736ce0572
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / intigriti-0.1.0-py3-none-any.whl
| Download URL | intigriti-0.1.0-py3-none-any.whl |
|---|---|
| Size | 23.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bc1d2c412b159e7efa5e76f2e104f301a9fde1e50f9cf9c5d5a4242658bd1989
|
|
BLAKE2b-256 checksum How to use checksums |
61fae9248a4d7a02ee1d758dfded90e2b0ff44481068e8adf118e72ead2a9a0c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log