Skip to main content

vainu-cli

PyPI version Python versions License: MIT

CLI and Python client library for the Vainu company data API. Query Nordic company data, export large datasets, and integrate Vainu into your workflows.

New here? Start with Get started — copy one command, sign in, run your first search. No coding experience required.


Get started

You will copy one command, sign in with your browser, and run your first company search.

Time: about 5 minutes.

Step 1 — Install

Pick the section for your computer. The installer uses uv to handle Python for you — you do not need to install Python yourself.

Mac

  1. Open Terminal (press Cmd + Space, type Terminal, press Enter).
  2. Copy and paste this entire line, then press Enter:
curl -LsSf https://raw.githubusercontent.com/vainu-app/vainu-cli/main/scripts/install.sh | sh
  1. Wait until you see Next steps: — installation is done.
  2. Close Terminal and open it again (so the vainu command is recognized).

Windows

  1. Open PowerShell (Start menu → type PowerShell → open Windows PowerShell).
  2. Copy and paste this entire line, then press Enter:
irm https://raw.githubusercontent.com/vainu-app/vainu-cli/main/scripts/install.ps1 | iex
  1. Wait until you see Next steps: — installation is done.
  2. Close PowerShell and open it again.

If Windows blocks the script, run PowerShell as your normal user (not Administrator) and try again.

Linux

Same as Mac — open a terminal and run:

curl -LsSf https://raw.githubusercontent.com/vainu-app/vainu-cli/main/scripts/install.sh | sh

Then close and reopen the terminal.

Step 2 — Sign in

In Terminal (Mac/Linux) or PowerShell (Windows), run:

vainu login

Your browser opens. Sign in with your Vainu account. When done, return to the terminal — you should see Logged in as ....

Step 3 — Verify

vainu doctor

You should see All checks passed. If something failed, the command prints what to fix.

Step 4 — Your first search

Look up a Finnish company by business ID:

Mac / Linux:

vainu organizations --payload "$(vainu examples path 08-simple-filtering)"

Windows (PowerShell):

vainu organizations --payload (vainu examples path 08-simple-filtering)

You get JSON with company details. That's it.

Troubleshooting

Problem Fix
'vainu' is not recognized Close and reopen Terminal / PowerShell. Still broken? Run install again.
Mac: command not found after install Add to ~/.zprofile: export PATH="$HOME/.local/bin:$PATH", then open a new window.
Linux: command not found after install Add to ~/.bashrc: export PATH="$HOME/.local/bin:$PATH", then open a new window.
Windows: command not found Close PowerShell completely and reopen. Check %USERPROFILE%\.local\bin exists.
Login browser does not open Run vainu login --no-browser and open the printed URL manually.
Signals commands fail Signals need OAuth — use vainu login, not an API key alone.
Want the latest CLI Run vainu update (or vainu upgrade).

Run vainu doctor anytime to diagnose install and auth issues.


Other install options

Requires Python 3.11+ — uv installs it for you if it is missing. The install scripts above pull from PyPI and fall back to GitHub while the package is not published yet, so the commands here are only needed for manual installs.

From a git checkout:

./scripts/install.sh --local      # Mac / Linux
.\scripts\install.ps1 -Local      # Windows (PowerShell)

Straight from GitHub with uv:

uv tool install git+https://github.com/vainu-app/vainu-cli.git

As a library dependency in a Python project:

uv add git+https://github.com/vainu-app/vainu-cli.git

To move to a later release once installed: vainu update (or vainu upgrade).

Using with AI agents? See AGENTS.md and the vainu-cli skill.


Quick start

CLI

Set your API key:

export VAINU_API_KEY=your-api-key

Search for a company:

vainu companies --query "?country=FI&business_id=FI01320292"

Export a large dataset to a file:

vainu companies-async \
  --query "?country=FI" \
  --format jsonl \
  --output finnish_companies.jsonl

Search organizations with a JSON payload. --payload takes inline JSON, a file path, or - to read stdin:

vainu organizations --payload '{"query": {"country": "SE"}}'
vainu organizations --payload payload.json
echo '{"query": {"country": "SE"}}' | vainu organizations --payload -

Python library

Async client:

import asyncio
from vainu_cli import VainuAPIKeyClient

async def main():
    client = VainuAPIKeyClient(api_key="your-api-key")
    try:
        result = await client.companies(payload="?country=FI&business_id=FI01320292")
        print(result)

        # Async export job — polls until complete
        async_result = await client.companies_async(
            payload="?country=FI", format="jsonl"
        )
        await async_result.download_to_file("companies.jsonl")
    finally:
        await client.close()

asyncio.run(main())

Sync client:

from vainu_cli import VainuAPIKeySyncClient

client = VainuAPIKeySyncClient(api_key="your-api-key")
try:
    result = client.companies(payload="?country=FI&business_id=FI01320292")
    print(result)
finally:
    client.close()

Authentication

API key (default)

export VAINU_API_KEY=your-api-key
vainu companies --query "?country=FI"

# or pass inline
vainu --api-key your-api-key companies --query "?country=FI"

OAuth 2.0 client credentials

export VAINU_CLIENT_ID=your-client-id
export VAINU_CLIENT_SECRET=your-client-secret
vainu --auth-method oauth companies --payload payload.json

Tokens are fetched and refreshed transparently, and the access token is cached between invocations so repeated commands skip the token round-trip. The cache lives in the OS keyring (Keychain / Secret Service / Credential Locker), falling back to a 0600 file under the user config dir when no keyring backend is available. Entries are keyed by base URL, client ID and scope, so several environments can be used side by side.

VAINU_TOKEN_CACHE=0 vainu --auth-method oauth companies ...  # mint a fresh token
VAINU_AUTH_STORE=file vainu --auth-method oauth companies ...  # skip the keyring
vainu auth logout                                            # drop cached tokens

A token that the API rejects with 401 is discarded and the request retried once, so a revoked token costs one extra call rather than an hour of failures.


CLI reference

vainu [OPTIONS] COMMAND [ARGS]...

Options:
  --auth-method [apikey|oauth|jwt]  Authentication method (default: apikey)
  --api-key TEXT                    API key (or VAINU_API_KEY)
  --client-id TEXT                  OAuth client ID (or VAINU_CLIENT_ID)
  --client-secret TEXT              OAuth client secret (or VAINU_CLIENT_SECRET)
  --base-url TEXT                   Override API base URL
  --async-mode / --no-async-mode    Use async client for search commands
  -v, --verbose                     Enable DEBUG logging
  --version                         Show version and exit

Commands:
  companies             Fetch company data
  companies-async       Export company data via async job
  organizations         Fetch organization data
  organizations-async   Export organization data via async job
  organizations-count   Count matching organizations without returning rows
  fields                Inspect organization field metadata
  enrichment-agent      Run an enrichment agent prompt against one company
  signals-news          Fetch news signals
  signals-data-changes  Fetch company data-change signals
  lists                 Manage organization lists (static and dynamic)
  update                Upgrade vainu-cli to the latest PyPI release
  upgrade               Alias for update

vainu companies

--query TEXT           Query string, e.g. "?country=FI"
--payload JSON/FILE/-  Inline JSON, a file path, or "-" for stdin
--payload-path ...     Alias for --payload
--format               json | csv | jsonl  (default: json)
--stream/--no-stream   Stream lines as they arrive (default on for csv/jsonl)
--output FILE          Write to file instead of stdout

vainu companies-async

Submits an async export job, polls until complete, and downloads the result.

--query TEXT           Query string
--payload JSON/FILE/-  Inline JSON, a file path, or "-" for stdin
--payload-path ...     Alias for --payload
--format               json | csv | jsonl  (default: json)
--output FILE          Output file (required)
--poll-interval INT    Polling interval in seconds (default: 3)
--timeout INT          Max wait seconds (default: 14400)

vainu organizations / vainu organizations-async

Same options as the company commands (organizations always use POST with a JSON payload). organizations takes --stream / --no-stream too; the -async export commands do not, since they already download to a file in chunks.

organizations and organizations-async also accept --payload-path as an alias for --payload.

To see which field paths you can put in query vs the fields output list, use vainu fields organizations.

vainu organizations-count

Counts the organizations matching a query without returning any rows — the cheap way to size a segment before exporting it. Takes the same query and database as organizations, but fields, limit and offset are ignored: only count metadata comes back.

A payload written for organizations can be passed straight to organizations-count. order is stripped before sending, because the count endpoint rejects it with 400 invalid order by value for any value — sorting is meaningless when only a total comes back.

--payload JSON/FILE/-  Inline JSON, a file path, or "-" for stdin
--payload-path ...     Alias for --payload
--database TEXT      Country database, e.g. FI, SE, NO or DK
--list TEXT          Count a saved Vainu list (supplies its own query and database)
--recount/--no-recount   Force a fresh count instead of reusing the cached one
--max-cache-age INT  Recount if the cached count is older than this many seconds
--wait/--no-wait     Keep re-requesting until the count is ready (default: --wait)
--poll-interval INT  Polling interval in seconds (default: 3)
--timeout INT        Max wait seconds (default: 14400)
--language TEXT      Accept-Language header
--output FILE        Write to file instead of stdout

--database, --list, --recount and --max-cache-age override the same keys in --payload, so you can keep the query in a file and vary only the country:

vainu organizations-count --payload "$(vainu examples path 01-amount-of-companies)"
vainu organizations-count --payload "$(vainu examples path 01-amount-of-companies)" --database SE

# count a saved list without writing any JSON
vainu organizations-count --list 68a1f2c9abcdef0123456789

# inline JSON, no file needed
vainu organizations-count --payload '{"query": {"?GTE": {"financial_data.revenue": 1000000}}, "database": "FI"}'

# just the number, for scripting
COUNT=$(vainu organizations-count --list 68a1f2c9abcdef0123456789 | jq .count)

The response is count metadata, not rows:

{
  "count": 180086,
  "time": "2026-04-16T15:21:16.445551",
  "status": "ready",
  "duration": 164.5,
  "rate_of_change": null,
  "eta_utc": null
}

About --wait. The API computes counts in the background and defaults to async: true, so a cold cache answers {"count": null, "status": "scheduled"} and the finished count is collected by re-sending the same payload. --wait (the default) does that for you until status leaves scheduled/process, tolerating a few transient network failures on the way. --no-wait prints that first reply as-is, which is what you want when you only need status and eta_utc:

vainu organizations-count --list 68a1f2c9abcdef0123456789 --recount --no-wait

A status of error prints the body and exits non-zero.

vainu update / vainu upgrade

Upgrade the CLI to the latest PyPI release. Prefers uv tool upgrade vainu-cli (what the install scripts use); falls back to pip install --upgrade vainu-cli if uv is not on PATH.

vainu update
vainu upgrade

vainu fields

Lists organization field metadata from GET /v3/organizations_fields/ — names, types, and whether each path is filterable (usable in a VQL query) or output (usable in the fields list). The response is a catalog, not company records.

vainu fields organizations

--view               table | summary | json  (default: table)
--output FILE        Write the view to a file instead of stdout
--api-version TEXT   API version query param (default: v3)
--category TEXT      Only this main_category (e.g. basic, contacts)
--database TEXT      Only fields available for FI | SE | NO | DK | NL
--search TEXT        Case-insensitive match on path, English name, or description
--filterable         Only fields usable in VQL filters (filter/search)
--returnable         Only fields usable in the fields output list (export/profile)
--permission-gated   Only fields that require an extra account entitlement
vainu fields organizations
vainu fields organizations --filterable --search revenue
vainu fields organizations --returnable --category contacts
vainu fields organizations --permission-gated --view summary
vainu fields organizations --view json --output fields.json

The default table view is grep-friendly: path, name, filterable, output, permission, type. summary prints counts by category and lists permission gates. json returns the (optionally filtered) API payload, including allowed operators.

Filterable means application_availability includes filter or search. Output means export or profile. Some paths — contact email/phone, payment delays, vehicles, real estate — are listed with a requires_permission slug and stay inaccessible until the account is entitled to them.

Streaming

--format jsonl and --format csv stream by default: each line is written the moment it arrives, rather than the command sitting silent for a minute and then printing everything at once. Nothing larger than a single line is held in memory, so piping a large export into another tool costs almost nothing:

vainu organizations --payload payload.json --format jsonl | jq -r .business_id
vainu signals-news --payload payload.json --format jsonl --output signals.jsonl
  • --format json never streams — a JSON body is a single document and is not valid until its last byte. Asking for --stream --format json is an error; leaving the flag off just buffers.
  • --no-stream forces the old buffered behaviour for jsonl/csv.
  • Works under both the default sync client and --async-mode.
  • With csv, the first line out is the header row.
  • Streaming drops blank lines and, with --output, always ends the file with a newline; --no-stream copies the body verbatim, trailing byte included. The rows themselves are the same either way.

vainu enrichment-agent

Runs an enrichment agent prompt against one company and prints the structured fields the prompt defines. The prompt itself is built in the Vainu UI — this command only needs its id.

--prompt TEXT           Enrichment agent prompt id from the Vainu UI (required)
--business-id TEXT      Company business id, e.g. FI23365096 (required)
--database TEXT         Country database: FI | SE | NO | DK (required)
--refresh/--no-refresh  Re-run instead of reusing the cached answer
--payload JSON/FILE/-   Inline JSON, a file path, or "-" for stdin (alternative to the flags above)
--payload-path ...      Alias for --payload
--format                json | jsonl  (default: json)
--language TEXT         Accept-Language header
--request-timeout INT   HTTP timeout in seconds (default: 121)
--output FILE           Write to file instead of stdout
vainu enrichment-agent --prompt 12345 --database FI --business-id FI23365096
{
  "response": {
    "main_business_activity": "Supercell Oy is a mobile game developer that creates …",
    "products_and_services": "Supercell's primary products are its mobile games …"
  }
}

The keys under response are whatever the prompt was configured to return, so they differ per prompt. Notes worth knowing:

  • Like the rest of the v3 API, this needs an OAuth or JWT token — not a static API key.
  • Results are cached per prompt + company. A cached answer comes back quickly and spends no Vainu agent credits; --refresh forces a fresh run and does spend them.
  • An uncached run researches the company on the spot and can take minutes. Raise --request-timeout for those; the run continues server-side either way, so a re-run after a timeout usually returns the now-cached answer.
  • --prompt / --database / --business-id may come from --payload instead, and any flag you also pass overrides the file. That makes a saved payload reusable: keep the prompt id and database in the file and vary --business-id per run.
  • There is no --stream — a single enrichment result is one document, not a row stream.

vainu signals-news / vainu signals-data-changes

Fetch signals from the v3 Signals API (beta): signals-news returns externally sourced events (news, press releases, contract notices), signals-data-changes returns events generated from changes in company records.

--payload JSON/FILE/-  Inline JSON, a file path, or "-" for stdin (required)
--payload-path ...     Alias for --payload
--format               json | jsonl  (default: json)
--stream/--no-stream   Stream lines as they arrive (default on for jsonl)
--language TEXT        Accept-Language header
--output FILE          Write to file instead of stdout

Both endpoints require an OAuth or JWT token — the v3 API does not accept a static API key. Run vainu login, or set VAINU_CLIENT_ID / VAINU_CLIENT_SECRET and pass --auth-method oauth.

Payload keys: query (required — see the filtering query language), limit (default 20, capped at 100) and offset (capped at 100000). Notes worth knowing:

  • The response is a bare JSON array, newest first. There is no count or next, and order is not supported — sending it returns 400 invalid order by value.
  • csv is not available; use --format jsonl for exports and page with limit/offset until a page returns fewer rows than limit.
  • Signal types are integer tags ids, shared by both endpoints and by the Vainu UI. The full list is at TAGS_BY_TYPE.json.
  • Relative dates need plural units: "30 days ago" and "1 years ago" work, "1 year ago" returns 400.
  • News signals can be filtered by content, title, link, type and countries; data-change signals only by tags, vainu_date, business_ids and prospects.
  • business_ids takes country-prefixed ids (FI25578642) and supports ?IN only.
  • An empty array means "nothing matched" or "the query timed out", and an unknown field name is ignored rather than rejected — so always include a date bound and check field spelling when a result looks too large or too empty.

vainu lists

Manage saved organization lists — both dynamic lists (membership from a VQL query, shown as "My Lists" in the Vainu UI) and static lists (a fixed set of business IDs, shown as "Custom Lists").

Requires OAuth, JWT, or vainu loginnot a static API key alone.

vainu lists                              List all lists (static + dynamic)
vainu lists get ID                       Retrieve one list by id
vainu lists delete ID                    Delete any list by id

vainu lists static                       List static lists
vainu lists static get ID
vainu lists static create --payload JSON/FILE/-   Required: name, country (FI|SE|NO|DK|NL)
vainu lists static update ID --payload JSON/FILE/-
vainu lists static add ID --payload JSON/FILE/-     JSON array of business IDs
vainu lists static remove ID --payload JSON/FILE/-
vainu lists static delete ID

vainu lists dynamic                      List dynamic lists
vainu lists dynamic get ID
vainu lists dynamic create --payload JSON/FILE/-  Required: name, country, query (serialized VQL)
vainu lists dynamic update ID --payload JSON/FILE/-
vainu lists dynamic delete ID

--format             json | jsonl  (default: json)
--output FILE        Write JSON response to file (create/update/get/list commands)

List all accessible lists and grab an id for export:

vainu lists
vainu lists get 63d8de4eb7dfe9f5896fa539

Create a dynamic list (query is a JSON string, not a nested object):

cat > dynamic.json <<'EOF'
{
  "name": "FI companies with 500+ employees",
  "country": "FI",
  "query": "{\"?GTE\": {\"financial_data.employees.absolute_count\": 500}}"
}
EOF
vainu lists dynamic create --payload dynamic.json

Create a static list and add/remove companies (replace placeholder business IDs with your own):

vainu lists static create --payload - <<'EOF'
{"name": "Targets", "country": "FI", "business_ids": ["FI01234567"]}
EOF

echo '["FI07654321"]' | vainu lists static add LIST_ID --payload -
echo '["FI01234567"]' | vainu lists static remove LIST_ID --payload -

Rename or replace membership via update:

echo '{"name": "Renamed list"}' | vainu lists static update LIST_ID --payload -
echo '{"business_ids": []}' | vainu lists static update LIST_ID --payload -   # clear all members

Export every company in a saved list (swap in your list id from vainu lists):

vainu organizations-async \
  --payload example_payloads/organizations_api/04-get-all-companies-in-vainu-list-async-sync.json \
  --format jsonl \
  --output list_companies.jsonl

Notes:

  • Static lists support atomic add and remove (PATCH .../add/ and .../remove/); dynamic lists do not — edit the query with vainu lists dynamic update instead.
  • country is immutable after creation.
  • Each business_id must match the list country prefix (FI…, SE…, etc.).
  • Add/remove payloads must be a JSON array of ids, e.g. ["FI01234567"], not an object.

Example payloads

The repository ships ready-made Organizations API request bodies under example_payloads/organizations_api/, transcribed from the v3 recipes. Each file is a complete POST body — query, fields, database, limit and friends at the top level — so it can be handed straight to --payload. The paths below assume you have cloned the repo.

Each file also carries a _meta block naming the recipe it came from. It is sent along with the rest of the body and the API ignores it; drop it if you prefer a minimal request.

File What it shows Notes
01-amount-of-companies-matching-the-query.json Counting NO companies with revenue ≥ 1M Use with vainu organizations-count
02-filter-contacts-return-only-ceos-of-companies.json Subdocument aggregation returning only CEO / Privacy Officer contacts
03-geospatial-business-unit-search-returning-only-matching-business_units.json Geo sphere search with unwind_subdocument — only matching business units
04-get-all-companies-in-vainu-list-async-sync.json Every company in a saved Vainu list list is a placeholder ID — swap in your own
05-get-count-of-vehicles-with-make-and-registration.json Per-company Skoda count via ?FILTER_SUBDOCUMENTS + ?COUNT limit is 1000000 — lower it before running interactively
06-simple-fuzzy-search-api.json Fuzzy free-text search for "volvo" Targets /v3/organizations/search/, which the CLI does not reach
07-search-companies-with-geo-sphere-with-coordinates.json ?GEO_WITHIN_SPHERE returning whole companies Radius is in radians (metres ÷ 6371000)
08-simple-filtering-example-using-v3organizations.json Minimal exact-match filter on business_id Start here
09-simple-oauth-client-credentials-example.json The same filter, run under OAuth client credentials
10-technology-search-shopify.json ?STARTSWITH on technology_data.name
11-track-modifications.json ?RANGE over modifications.* for incremental sync Contains <NOW_MINUS_10_SECONDS_ISO8601> — substitute both timestamps before sending
12-count-companies-in-vainu-list.json Counting every company in a saved list Use with vainu organizations-count; list is a placeholder ID

Set up credentials once (see Authentication), then run the smallest example:

export VAINU_API_KEY=your-api-key
vainu organizations \
  --payload example_payloads/organizations_api/08-simple-filtering-example-using-v3organizations.json

Aggregations use the same command — only the payload changes:

vainu organizations \
  --payload example_payloads/organizations_api/02-filter-contacts-return-only-ceos-of-companies.json

Add the global -v before the command to see request timing, and --language after it to localise the response:

vainu -v organizations \
  --language fi \
  --payload example_payloads/organizations_api/10-technology-search-shopify.json

--async-mode runs the same request through the httpx client instead of requests:

vainu --async-mode organizations \
  --payload example_payloads/organizations_api/07-search-companies-with-geo-sphere-with-coordinates.json

With --output the result is written to a file and the confirmation goes to stderr, so stdout stays clean for piping:

vainu organizations \
  --payload example_payloads/organizations_api/03-geospatial-business-unit-search-returning-only-matching-business_units.json \
  --output uppsala_units.json

--format csv returns the raw CSV body untouched:

vainu organizations \
  --payload example_payloads/organizations_api/05-get-count-of-vehicles-with-make-and-registration.json \
  --format csv \
  --output skoda_counts.csv

Large result sets belong in an async export job, where --output is required:

vainu organizations-async \
  --payload example_payloads/organizations_api/04-get-all-companies-in-vainu-list-async-sync.json \
  --format jsonl \
  --poll-interval 5 \
  --output list_companies.jsonl

Any payload works under OAuth client credentials instead of an API key:

export VAINU_CLIENT_ID=your-client-id
export VAINU_CLIENT_SECRET=your-client-secret
vainu --auth-method oauth organizations \
  --payload example_payloads/organizations_api/09-simple-oauth-client-credentials-example.json

Signals API payloads

Signals request bodies live under example_payloads/signals_api/. Each file is a complete POST body — query, limit, offset at the top level.

File What it shows Notes
01-news-signals-for-one-company.json Every news signal for one company over the last year Start here. Relative dates need plural units
02-news-signals-by-tags.json Funding + M&A by signal type id Values inside one ?IN are OR-ed
03-news-signals-excluding-tags.json Excluding a noisy type, keeping only tagged signals ?NOT alone also lets untagged signals through, hence ?EXISTS
04-news-signals-keyword-monitor.json Keyword search over signal content in a date window News only; matching starts at word boundaries
05-data-changes-for-companies.json New financial statements and CEO changes for two companies Data changes have no countries field and no text filtering
06-data-changes-jsonl-paging.json First page of a JSON Lines export Raise offset by limit until a page is short
export VAINU_CLIENT_ID=your-client-id
export VAINU_CLIENT_SECRET=your-client-secret

vainu signals-news \
  --payload example_payloads/signals_api/01-news-signals-for-one-company.json

--format jsonl writes one signal per line, streaming each one as it arrives (pass --no-stream to buffer the whole page instead):

vainu signals-data-changes \
  --payload example_payloads/signals_api/06-data-changes-jsonl-paging.json \
  --format jsonl \
  --output data_changes.jsonl

Enrichment Agent API payloads

File What it shows Notes
01-run-enrichment-agent-for-one-company.json Running one prompt against one company prompt is CHANGEME — swap in the prompt id from your Vainu UI
vainu enrichment-agent \
  --payload example_payloads/enrichment_agent_api/01-run-enrichment-agent-for-one-company.json \
  --prompt 12345 \
  --business-id FI01320292

Python API

Async clients

Class Auth
VainuAPIKeyClient(api_key, base_url?, language?, timeout?) Static API key
VainuOAuthAPIClient(client_id, client_secret, scope?, base_url?, language?, timeout?) OAuth 2.0

Methods (all async):

Method Description
companies(payload, format) Fetch company data (dict for json, raw str for csv/jsonl)
companies_async(payload, format) Submit async job → AsyncResult
organizations(payload, format) Fetch organization data (dict for json, raw str for csv/jsonl)
organizations_async(payload, format) Submit async job → AsyncResult
organizations_count(payload, wait?, poll_interval?, max_wait_seconds?) Count matching organizations (dict of count metadata). Drops order, which the endpoint rejects. wait=True re-sends the payload until status leaves scheduled/process
organization_fields(api_versions?) Organization field catalog (list)
enrichment_agent(payload, format) Run an enrichment agent prompt on one company (dict for json)
signals_news(payload, format) Fetch news signals (list for json, raw str for jsonl)
signals_data_changes(payload, format) Fetch data-change signals (list for json, raw str for jsonl)
organization_lists(format) List all organization lists (list for json)
organization_list_get(list_id, format) Retrieve one list summary
organization_list_delete(list_id) Delete a list (static or dynamic)
organization_list_static_create(payload, format) Create a static list
organization_list_static_update(list_id, payload, format) Update a static list
organization_list_static_add(list_id, business_ids) Add business IDs to a static list
organization_list_static_remove(list_id, business_ids) Remove business IDs from a static list
organization_list_dynamic_create(payload, format) Create a dynamic list
organization_list_dynamic_update(list_id, payload, format) Update a dynamic list
stream_companies(payload, format) Context manager → line iterator (csv/jsonl)
stream_organizations(payload, format) Context manager → line iterator (csv/jsonl)
stream_signals_news(payload, format) Context manager → line iterator (jsonl)
stream_signals_data_changes(payload, format) Context manager → line iterator (jsonl)
close() Close HTTP connection

Sync clients

Class Auth
VainuAPIKeySyncClient(api_key, base_url?, language?, timeout?) Static API key
VainuOAuthSyncClient(client_id, client_secret, scope?, base_url?, language?, timeout?) OAuth 2.0

Same methods as the async clients but blocking (no await).

AsyncResult

result.download_url          # URL of the exported file
result.duration              # Job duration in seconds
await result.json()          # Download and parse as dict (async)
await result.download_to_file(path)  # Download via curl (async)

result.json()                # Download and parse as dict (sync)
result.download_to_file(path)        # Download via streaming requests (sync)

Search methods return parsed JSON only when format="json". For format="csv" and format="jsonl", they return the raw response text so the caller can write or stream it without JSON re-encoding. organizations_count takes no format — the endpoint renders only its JSON metadata object — and always returns a dict. The signals methods return a list under format="json" — the Signals API responds with a bare array rather than a page object — and accept json/jsonl only.

enrichment_agent returns the prompt's fields under response and has no stream_* variant, since one enrichment result is a single document. An uncached run can take minutes, so pass a higher timeout (seconds, default 121) to the client when you expect one:

client = VainuOAuthSyncClient(client_id="...", client_secret="...", timeout=600)
result = client.enrichment_agent(
    payload={"prompt": "12345", "database": "FI", "business_id": "FI23365096"}
)
print(result["response"])
client.close()

List management methods (organization_lists, organization_list_static_*, organization_list_dynamic_*) require OAuth/JWT/browser auth like signals and enrichment — not a static API key.

import json
from vainu_cli import VainuOAuthSyncClient

client = VainuOAuthSyncClient(client_id="...", client_secret="...")
lists = client.organization_lists()
print(lists[0]["id"], lists[0]["name"])

created = client.organization_list_dynamic_create({
    "name": "Big FI companies",
    "country": "FI",
    "query": json.dumps({"?GTE": {"financial_data.revenue": 1_000_000}}),
})
client.organization_list_delete(created["id"])
client.close()

Streaming

The stream_* methods are (async) context managers yielding an iterator of lines, so rows can be handled while the server is still producing them and no full body is ever held in memory. Lines arrive as str with the newline stripped and blank lines skipped; the response is released when the with block exits, even if you stop iterating early.

import asyncio, json
from vainu_cli import VainuOAuthAPIClient, VainuOAuthSyncClient

# sync
client = VainuOAuthSyncClient(client_id="...", client_secret="...")
with client.stream_signals_news(payload={"query": {}}, format="jsonl") as lines:
    for line in lines:
        print(json.loads(line)["title"])
client.close()

# async
async def main():
    client = VainuOAuthAPIClient(client_id="...", client_secret="...")
    async with client.stream_organizations(payload={"query": {}}, format="jsonl") as lines:
        async for line in lines:
            print(json.loads(line)["business_id"])
    await client.close()

asyncio.run(main())

format defaults to jsonl here and must be a line-oriented format — format="json" raises ValueError, since a single JSON document only becomes valid once its last byte lands. With csv the first line is the header row. Error bodies behave as they do on the buffered methods: 400/403/404 are yielded as the response text rather than raised, other failures raise.


Environment variables

Variable Description
VAINU_API_KEY Static API key
VAINU_CLIENT_ID OAuth client ID
VAINU_CLIENT_SECRET OAuth client secret
VAINU_JWT_REFRESH_TOKEN JWT refresh token
VAINU_BASE_URL Override API base URL

Development

git clone https://github.com/vainu-app/vainu-cli.git
cd vainu-cli
uv sync --extra dev

# Run tests
uv run pytest -v

# Lint
uv run ruff check src/ tests/
uv run ruff format src/ tests/

# Security checks
uv run bandit -c pyproject.toml -r src/
uv run pip-audit

# Build
uv build

License

MIT © 2026 Vainu

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vainu_cli-0.1.1.tar.gz (156.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vainu_cli-0.1.1-py3-none-any.whl (72.0 kB view details)

Uploaded Python 3

File details

Details for the file vainu_cli-0.1.1.tar.gz.

File metadata

  • Download URL: vainu_cli-0.1.1.tar.gz
  • Upload date:
  • Size: 156.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vainu_cli-0.1.1.tar.gz
Algorithm Hash digest
SHA256 4d1642b6415ef5172c5ce81c27b33c9cf00ff269b4b84ae860c5f18bb35e25e2
MD5 6da52fe5ddafb910a7d7557fea39ba85
BLAKE2b-256 0c0c3576c309b2e5b0b8040d690a8fcc03e29977c0bbe8b5d7826b3069d5621f

See more details on using hashes here.

Provenance

The following attestation bundles were made for vainu_cli-0.1.1.tar.gz:

Publisher: publish.yml on vainu-app/vainu-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vainu_cli-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: vainu_cli-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 72.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vainu_cli-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 902e8c75dbae2c6e33b7595deeb17537638ab2b84b2355c645613f948734bb44
MD5 9f7578f86b6c79103dd058b5e2adea9c
BLAKE2b-256 9fdf19ac2c8744f643b1c31f9b903d633edbe65906869079ad2cf30b4c878da8

See more details on using hashes here.

Provenance

The following attestation bundles were made for vainu_cli-0.1.1-py3-none-any.whl:

Publisher: publish.yml on vainu-app/vainu-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.2

2 files

This release

0.1.1 This release

2 files

0.1.0

2 files

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