Skip to main content

Invoist for Python

The official Python library for the Invoist API. Send Invoist an invoice or receipt and get back what it says: the sender, the amounts, the dates, every line, and anything worth a second look. A household can read its bills and mark them paid.

PyPI Python

Install

pip install invoist

Python 3.11 or newer. It depends on httpx and Pydantic.

For a business

Read an invoice and wait for the result:

import os
from pathlib import Path
from invoist import Invoist

invoist = Invoist(os.environ["INVOIST_API_KEY"])
result = invoist.extract_and_wait(Path("invoice.pdf"))
print(result.extraction.vendor_name, result.extraction.total_cents, result.extraction.currency)

Create a key in Invoist under Integrations › API. It is shown once, so keep it in your secret store, and never ship it in an app or a web page.

For a household

A household's key (from Settings › AI assistants in Invoist Personal) works with its bills:

import os
from invoist import Invoist

invoist = Invoist(os.environ["INVOIST_API_KEY"])
for bill in invoist.bills.list(paid=False):
    print(bill.vendor_name, bill.total_cents, bill.currency, bill.due_date)

Amounts and dates

Money is always a whole number of minor units: total_cents=48000 with currency="EUR" is €480.00. There are no floats, so nothing gets rounded on the way. Dates are datetime.date, and times are timezone-aware datetime.datetime.

Every result is a typed Pydantic model, so your editor knows every field. When the API adds a field after this release, it is kept (model.model_extra) rather than causing an error, and a new value in a field like status comes through as a plain string.

Reading documents

extract returns as soon as Invoist has the file. Reading it takes a few seconds and happens in the background.

result = invoist.extract(Path("receipt.jpg"))
document_id = result.document.id

A file can be bytes, a pathlib.Path, an open binary file, or a tuple (filename, content) or (filename, content, content_type) when you want to choose the name Invoist shows.

Then get the result in one of three ways:

  • Wait for it: invoist.wait_for_extraction(document_id) checks every 2 seconds for up to 2 minutes (both adjustable with poll_interval and timeout). extract_and_wait does both steps at once.
  • Get a webhook when it's done, so nothing waits. See Webhooks.
  • Check yourself with invoist.get_document(document_id).

A document Invoist could not read is an answer, not an exception:

extraction = invoist.wait_for_extraction(document_id)
if extraction.status == "failed":
    print("Could not read it:", extraction.error_message)

Sending the same file twice is safe: Invoist returns the document it already has (result.duplicate is True) and does not bill you again. To retry a file that may differ slightly, such as one exported again, give it an idempotency_key; a key reused for a different file raises an error with status 422.

invoist.extract(Path("invoice.pdf"), idempotency_key="order-1042")

Up to 10 files in one call, each with its own outcome:

batch = invoist.extract_batch([Path("a.pdf"), Path("b.pdf")])
print(batch.accepted, batch.rejected)
for item in batch.results:
    print(item.filename, item.error.message if item.error else item.document_id)

Everything else for a business

invoist.list_documents(status="done", flagged=True, q="Helen", limit=50, offset=0)
invoist.get_document(document_id)               # the document, its extraction, its review items
invoist.get_document_file(document_id)          # FileDownload: .content, .content_type, .filename
invoist.list_review_queue(status="pending")     # what Invoist flagged for a person to check
invoist.resolve_review_item(item_id)            # or resolve_review_item(item_id, "dismissed")
invoist.list_categories()
invoist.get_usage()
invoist.get_stats()
invoist.list_webhook_endpoints()                # .endpoints and .known_events
invoist.create_webhook_endpoint("https://example.com/invoist", events=["extraction.completed"])
invoist.delete_webhook_endpoint(endpoint_id)

Everything for a household

from datetime import date

invoist.bills.list(paid=False, month=date(2026, 11, 1), sender="Helen", q="", limit=50)
invoist.bills.get(bill_id)                      # what was read, warnings, letters, who paid
invoist.bills.get_file(bill_id)                 # FileDownload
invoist.bills.summary()                         # unpaid, overdue, and what falls due soon
invoist.bills.stats(month="2026-10")            # a month's totals and biggest senders
invoist.bills.mark_paid(bill_id)                # needs a key allowed to mark bills paid
invoist.bills.mark_unpaid(bill_id)
invoist.held_emails.list()                      # forwarded emails waiting for approval

month takes "YYYY-MM" or any date in that month, and today a date; a datetime counts by its date in your local time.

Async

AsyncInvoist has the same methods, each awaitable:

import asyncio, os
from invoist import AsyncInvoist

async def main() -> None:
    async with AsyncInvoist(os.environ["INVOIST_API_KEY"]) as invoist:
        summary = await invoist.bills.summary()
        print(summary.unpaid, "unpaid,", summary.overdue, "overdue")

asyncio.run(main())

To cancel a call, cancel its task or wrap it in asyncio.timeout(...).

Create one client and reuse it: it keeps connections open. Close it with close() (aclose() for async) or use with / async with.

Errors

Every failure raises InvoistError: an error from the API, no connection, or a timeout.

from invoist import InvoistError

try:
    invoist.bills.mark_paid(bill_id)
except InvoistError as err:
    if err.is_permission_error:
        print("This key may only read bills.")
    else:
        print(err.status, err.code, err.message, err.request_id)
Field What it is
status The HTTP status, or 0 when no response arrived
code A stable code such as invalid_api_key or rate_limited. Branch on this.
message A human explanation. Its wording may change.
request_id Quote this when you contact support.
body The raw response body

There are also is_auth_error (401), is_permission_error (403), is_not_found (404), is_rate_limited (429) and is_retryable (no response, 429 or 5xx). With no response, code is network_error or timeout; when a server returns something that isn't Invoist's error format (a proxy's error page, say), it is unexpected_response.

Retries and timeouts

The client retries a request up to twice when there was no response, or the API answered 429 or 5xx. It waits as long as the Retry-After header says, otherwise 0.5s, then 1s, and so on (a little randomised, at most 8s). If the server asks for a wait longer than a minute, the call fails at once with that error instead of hanging. It never retries other errors, since sending the same bad request again would fail again. Uploads are retried too: they are safe to repeat.

invoist = Invoist(
    key,
    timeout=30,       # seconds per attempt (default 60)
    max_retries=0,    # turn retrying off (default 2)
)

To use a proxy, custom certificates or your own transport, pass an httpx.Client (or httpx.AsyncClient) as http_client.

Webhooks

Invoist signs every webhook with your endpoint's secret. verify_webhook checks the signature and the time it was sent, and returns the event, or raises InvoistError with status 400.

Always pass the raw request body, exactly as received. If you parse the JSON and turn it back into text, the bytes change and the check fails.

With Flask:

import os
from flask import Flask, request
from invoist import InvoistError
from invoist.webhooks import EventType, verify_webhook

app = Flask(__name__)

@app.post("/webhooks/invoist")
def invoist_webhook():
    try:
        event = verify_webhook(
            request.get_data(),                        # the raw body
            request.headers.get("X-Invoist-Signature"),
            os.environ["INVOIST_WEBHOOK_SECRET"],
        )
    except InvoistError:
        return "", 400
    if event.type == EventType.EXTRACTION_COMPLETED:
        print("Read document", event.data["document_id"])
    return "", 204

With FastAPI:

import os
from fastapi import FastAPI, HTTPException, Request, Response
from invoist import InvoistError
from invoist.webhooks import EventType, verify_webhook

app = FastAPI()

@app.post("/webhooks/invoist")
async def invoist_webhook(request: Request) -> Response:
    try:
        event = verify_webhook(
            await request.body(),                      # the raw body
            request.headers.get("X-Invoist-Signature"),
            os.environ["INVOIST_WEBHOOK_SECRET"],
        )
    except InvoistError:
        raise HTTPException(status_code=400) from None
    if event.type == EventType.EXTRACTION_COMPLETED:
        print("Read document", event.data["document_id"])
    return Response(status_code=204)

The event has id, type, created_at and data. The same event can arrive more than once, so use event.id to skip ones you've handled. EventType lists every type: DOCUMENT_CREATED, EXTRACTION_COMPLETED, EXTRACTION_FAILED, REVIEW_ITEM_CREATED, REVIEW_ITEM_RESOLVED and DOCUMENT_REQUEST_FULFILLED. The X-Invoist-Event and X-Invoist-Delivery headers carry the type and a delivery id.

A signature more than 5 minutes from now, in either direction, is refused (signature_too_old). Change that with tolerance= (seconds or a timedelta).

Development

python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/python scripts/generate.py   # rebuild invoist/_generated from openapi.yaml
.venv/bin/ruff check . && .venv/bin/ruff format --check . && .venv/bin/mypy
.venv/bin/pytest                       # INVOIST_API_KEY=... also runs read-only live tests

invoist/_generated/models.py is generated from the API's OpenAPI document ($INVOIST_OPENAPI, or ../../app/backend/openapi.yaml in the Invoist repository). Don't edit it by hand. testdata/conformance holds the test cases every Invoist SDK must pass.

Metadata

Release files for invoist 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for invoist 1.0.0
File Size Uploaded Provenance Actions
invoist-1.0.0.tar.gz 48.4 kB Without provenance Details for invoist-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for invoist 1.0.0
File Interpreter ABI Platform Actions
invoist-1.0.0-py3-none-any.whl Python 3 none any Details for invoist-1.0.0-py3-none-any.whl

Total release size: 84.1 kB

Release files / invoist-1.0.0.tar.gz

Download URL invoist-1.0.0.tar.gz
Size 48.4 kB
Tags Source
SHA-256 checksum
How to use checksums
4de48e4b7419334972c7fd6dc8b6b34d09ec02a97f62f6ef0eaa1e6da839ace9
BLAKE2b-256 checksum
How to use checksums
e5ea507657f79a77ea53005d5d08d023c9c59e5a04820b87939550951514ae24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Release files / invoist-1.0.0-py3-none-any.whl

Download URL invoist-1.0.0-py3-none-any.whl
Size 35.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1044e1657a1015c0b121e0367b01ebdc4a08c2b78585a78c66acbb397f882c84
BLAKE2b-256 checksum
How to use checksums
54645bebe1026883ded69ac0fe2f3fa0802a920b8fc1cee938e6449b477a9d0c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.8

Security for invoist 1.0.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page