Skip to main content

recovea

Install Recovea beside the model client you already use, and it reports the envelope of each call. Never a prompt, never a completion.

pip install recovea
from openai import OpenAI
from recovea import tap

client = tap(OpenAI(), tag="matter-4821")
# use client exactly as before
from anthropic import Anthropic
from recovea import tap

client = tap(Anthropic(), tag="matter-4821")

Your calls keep going straight to your provider. Nothing routes through Recovea, and no provider key is ever asked for. The wrapper reads the response your provider already returned, builds one small object from it, and posts that object on the side, after your call has returned. It never delays your call, never changes what your call returns, and never raises into your code.

Supported call sites: OpenAI chat.completions.create and responses.create, Anthropic messages.create, sync and async clients, streaming and non streaming.

What the tap sends

One event per call, recovea-tap-v1. This is the whole object. The published schema is at https://recovea.ai/docs/passive-tap

field type where it comes from
schema string always recovea-tap-v1
provider openai anthropic google xai mistral other your client's base URL, then its class name
model string, 1 to 128 chars the model your provider says it served
request_id string, 1 to 128 chars the provider's response id or request id header, otherwise a generated UUID
input_tokens integer the provider's usage field
output_tokens integer the provider's usage field
cached_tokens integer the provider's usage field (cache reads)
stop_reason stop length tool_calls content_filter error other the provider's stop or finish reason, mapped to this list
tool_calls integer how many tool calls the response contained. The count only
status integer 100 to 599 the HTTP status of the call. 599 when the call failed with no HTTP response, such as a transport failure or a timeout; 200 is assumed for a successful response the SDK returns without a status
retries integer always 0 in v1 (no SDK exposes a count)
latency_ms integer measured by the wrapper, start of call to end of response
rate_limit object or null the provider's rate limit response headers. Null unless you pass rate_limit_headers=True
timestamp RFC 3339 with timezone when the event was built
tag string, 1 to 64 chars of [A-Za-z0-9._:-] the tag you passed to tap(). The only free text field

What the tap never sends

Prompts. Messages. System prompts. Completions. Text, image, audio or file content of any kind. Tool definitions. Tool arguments. Tool results. Embeddings. Your provider key. Your Recovea key is a header, never a field.

The allowlist above is enforced twice: the SDK builds the object field by field and validates it before it leaves your process, and the server re-checks every field on arrival and rejects a batch that carries anything else, without logging it. A field that is not on the published page is not in the object.

See exactly what is sent

RECOVEA_TAP_INSPECT=1 (or inspect=True) prints every batch to stderr as the exact JSON body the endpoint would receive, and transmits nothing.

It needs no tap key, so you can read the fifteen fields on your own machine before any of them leave it.

One line on stderr says the mode is on; unset the variable and delivery works exactly as it did before.

If a tap key is configured as well, inspect mode still wins: nothing is transmitted, so a Baseline you are watching stays on "waiting for your first event" until you turn inspect mode off. That stderr line says so in as many words when both are set.

Options

client = tap(
    OpenAI(),
    tag="matter-4821",  # required, 1 to 64 chars of [A-Za-z0-9._:-]
    key=None,  # default: RECOVEA_TAP_KEY
    endpoint=None,  # default: RECOVEA_TAP_ENDPOINT
    flush_interval_ms=2000,
    max_queue=1000,  # oldest dropped first
    on_drop=None,  # called with a count only, never the event
    rate_limit_headers=False,  # see below
    inspect=None,  # default: RECOVEA_TAP_INSPECT. A bool overrides it both ways
)

One tag per wrapped client. Wrap a second client if you need a second tag. An invalid tag or provider raises at tap() time, never at request time.

Delivery is in memory and best effort: events are queued, posted in batches of up to 100 by a background thread, and flushed every 2,000 ms and whenever 100 are waiting. Nothing about delivery can reach your call path.

A 429 or a 503 means not now, and posting the same batch again is safe after either. The tap keeps the batch and the queue behind it, waits the answer's Retry-After (whole seconds or an HTTP date, never less than 200 ms and never more than 60 seconds a wait), and posts the same batch again. After a 503 each wait is at least double the one before, so a Retry-After of 5 gives waits of 5, 10, 20, 40 and then 60 seconds. With no readable Retry-After the waits are 200 ms, 800 ms, and then double. A batch gets at most eight attempts. Any other failure worth retrying (a 408, another 5xx, or no answer at all) is retried twice, after 200 ms and 800 ms.

A batch that is still not accepted is dropped, and the tap writes one line to stderr naming the last answer, the attempts and the events lost:

recovea tap: a batch was not accepted after 8 attempts (last answer 503); 100 events were not reported.

A 401 or a 410 writes its own line instead, below. The queue holds 1,000 events and drops the oldest first, counted through on_drop; the ones it drops while a batch waits out a 429 or a 503 are counted in a line of the same kind. On the way out of the process there is one more flush, a single attempt for each batch, a waiting one included, with a 1.5 second deadline, so the tap is never the reason a process is slow to exit.

How fast it catches up. One wrapped client posts at most four batches a second on a clean path: the drain leaves at least 250 ms between the starts of two posts, so a burst is delivered steadily rather than as fast as the network allows. A single batch waits for nothing unless it follows another within that 250 ms. Two things are outside that number and are meant to be. A post that is retried makes its extra attempts inside the same gap, so an endpoint answering 429 or 503 can see up to eight requests where the pacing counts one, each sent no sooner than its Retry-After asked. Pacing the retries as well would only make the tap slower to obey it. And the pacing is per wrapped client, so if you wrap a second client for a second tag, the two pace themselves independently. The ceiling that follows is about four hundred events a second per wrapped client, at the 100-event batch. Above that the queue fills to max_queue (1,000) and the oldest events are dropped and counted through on_drop — the tap sheds load rather than growing without limit, and it never slows your own calls down.

If the endpoint answers 410, or answers 401 three times in a row without a success in between, the tap stops sending — the queue is dropped, nothing more is posted, and one line to stderr says why — one line per reason, for the whole process, however many clients you have wrapped, so two clients that stop for the same reason say it once and a later, different ending still gets said — while your client goes on working exactly as it did. A 410 says why: when your Baseline has closed, the line is the server's own sentence; for any other ending the tap prints a line of its own.

A Baseline that has closed is the one that comes back. Leave the tap line exactly where it is and start a plan: reporting starts again by itself within about a minute of your application's next call, with no restart, no second line, and nothing to re-integrate. Only events from then on are sent — whatever was queued when the Baseline closed was dropped and counted through on_drop, and it is not replayed. While it is waiting, the tap tries the endpoint again at most once a minute, and only when your own traffic gives it something to send. A plan that ends returns the same closed answer, and starting again resumes reporting the same way.

An organization whose data was erased does not come back. Its key is not accepted any more, so the tap sees 401 three times and stops for the rest of the process. A 410 whose reason cannot be read is treated as an ending too: one neutral line that tells nobody to remove anything, and a pointer to the page in Recovea.

If no key is configured the client still works and the tap reports nothing, with one line to stderr when you construct it — unless inspect mode is on, which needs no key and prints every batch instead of reporting nothing (see "See exactly what is sent" above).

rate_limit_headers=True reads the provider's rate limit headers through the SDK's .with_raw_response path, which means the wrapper calls that method and hands you back its .parse() result. That is the same object the plain call returns, but it is a different code path inside the provider SDK, so it is off by default and rate_limit is null until you turn it on. The JavaScript tap defaults it off too, so the same call produces the same event shape in both SDKs.

Environment

variable meaning
RECOVEA_TAP_KEY your tap key, shown once in the product. Sent as Authorization: Bearer <key>
RECOVEA_TAP_ENDPOINT where events are posted. Default https://platform-api.recovea.ai/tap/v1/events
RECOVEA_TAP_INSPECT set to 1, true, yes or on (case and surrounding spaces ignored) to print every batch to stderr and send nothing. Any other value, and unset, leaves it off. No key needed

Requirements

Python 3.9 or newer. No dependencies.

Learn more: https://recovea.ai


(c) 2026 Recovea, Inc. MIT License.

Release files for recovea 0.1.4

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

Source distribution (sdist)

Source distribution for recovea 0.1.4
File Size Uploaded
recovea-0.1.4.tar.gz 88.8 kB Details

Built distribution (wheel)

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

Total release size: 134.6 kB

Release files / recovea-0.1.4.tar.gz

Download URL recovea-0.1.4.tar.gz
Size 88.8 kB
Tags Source
SHA-256 checksum
How to use checksums
e5facc39089ed22f0566c06954b09394ffb8a3929f4b08562a662a8026183359
BLAKE2b-256 checksum
How to use checksums
d75dd7cfd4788aa35352c4cc04ebdcb0d4ab74b988adf5f30e1ccd7e11e63bf4
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 26, 2026.

Transparency log

Release files / recovea-0.1.4-py3-none-any.whl

Download URL recovea-0.1.4-py3-none-any.whl
Size 45.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
69d0616fe074b3b6e41fbfcbecceb674153975d388a51dd887372afcab810c9a
BLAKE2b-256 checksum
How to use checksums
360b7d74fc64505f78c57fe145bebed875c6f9df896674e4b8a8624e928d76d9
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 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.0.2

2 release 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