Skip to main content

Arkova Python SDK

Typed Python client for the Arkova Verification APIs.

Install

pip install 'arkova>=2.6.0'

Python 3.10 or newer is supported.

Supported methods

  • anchor(data=None, *, fingerprint=None, file_mime=None)
  • anchor_bulk(inputs, *, dry_run=None, duplicate_strategy=None, batch_id=None)
  • fingerprint(data)
  • search(q, type="all", cursor=None, limit=50)
  • search_public(query, *, type=None, limit=None) for anonymous public search across organizations, members, secured records and public-record imports
  • create_attestation(*, attestation_type, subject_identifier, attester_name, claims, ...) to create an attestation (POST /api/v1/attestations); recorded as an individual's own statement unless an organization administrator asks for an organizational attester_type
  • verify(public_id)
  • verify_fingerprint(fingerprint)
  • list_anchors(...) for API-key-scoped private record listing with pagination and optional time or private-tag filters
  • get_anchor(public_id)
  • get_anchor_submission_status(public_id)
  • anchor_import(rows, *, action, description=None, user_tags=(), organization_tags=()) for 1 to 100 already-fingerprinted spreadsheet rows (never retried automatically)
  • get_merkle_proof(public_id), get_record(public_id), get_fingerprint(fingerprint), get_document(public_id), get_organization(public_id)
  • list_folders(...), create_folder(...), update_folder(...), bind_folder_connector(...), delete_folder(...), move_records(...), move_records_by_public_id(...)
  • import_source_preview(...) and import_source_confirm(...) for external record sources (need a short-lived user_access_token; never retried automatically)
  • register_agent, list_agents, get_agent, update_agent, revoke_agent, create_agent_key, admit_computeid_agent (see "Agent lifecycle")
  • list_orgs()

AnchorImportResultRow.recipient_status and the 500-character description check in anchor_import are included from 2.6.1 onward (see CHANGELOG.md). Check the installed version before using them.

Quick start

import os
from arkova import Arkova

with Arkova(api_key=os.environ["ARKOVA_API_KEY"]) as arkova:
    results = arkova.search("registered nurse", type="record", limit=5)
    for item in results.results:
        print(item.public_id, item.snippet)

Public search (no API key required)

search_public() calls the anonymous GET /api/v1/public/search route. It returns four groups (orgs, members, anchors, imports) that only ever carry public identifiers and URLs, never internal ids or emails. A configured API key is still sent and only raises your rate limit; a client constructed with api_key="" sends no Authorization header for this call.

from arkova import Arkova

with Arkova(api_key="") as arkova:
    page = arkova.search_public("acme university", type="orgs", limit=5)
    for org in page.results.orgs:
        print(org.display_name, org.verified, org.profile_url)
    if page.unavailable:
        print("partial results, unavailable groups:", page.unavailable)

query is trimmed and must be 2 to 200 characters, type is one of "all", "orgs", "members", "anchors", "imports", and limit is 1 to 50 per group. Invalid input raises ArkovaError(code="invalid_public_search_query") before any network call. A 503 with code="search_unavailable" means every requested group failed. AsyncArkova.search_public() has the same contract.

Anchor a document (HAKI-REQ-02)

anchor() fingerprints data client-side (SHA-256, in-process — the raw content is never sent, only the 64-char hex fingerprint) and submits it for network anchoring. Pass a pre-computed fingerprint instead if you already hashed the document elsewhere.

from arkova import Arkova

with Arkova(api_key="ak_live_...") as arkova:
    # Raw content — fingerprinted in-process before anything is sent.
    receipt = arkova.anchor("document content goes here")
    print(receipt.public_id, receipt.status)  # "PENDING" -> "SUBMITTED" -> "SECURED"
    print(receipt.record_uri)  # public verification page for this anchor

    # Or: you already have the fingerprint.
    receipt = arkova.anchor(fingerprint="a" * 64)

The same fingerprint always returns the same public_id — anchoring identical content twice is a no-op. file_mime, when supplied, is sent as source-document metadata; the document bytes remain local.

Use get_anchor_submission_status(public_id) for authenticated, caller-scoped queue or instant-processing state:

with Arkova(api_key="ak_live_...") as arkova:
    status = arkova.get_anchor_submission_status(receipt.public_id)
    print(status.action, status.anchor_status, status.instant_status, status.retryable)

AsyncArkova.get_anchor_submission_status(public_id) provides the same contract asynchronously. Current servers include action, credit_state, instant_status, and idempotent on new receipts; the receipt model keeps these fields optional so retained responses from older deployments remain parseable. The dedicated status model is the strict source for the current durable state.

Bulk-anchor documents (HAKI-REQ-02)

anchor_bulk() anchors up to BULK_ANCHOR_MAX_ROWS (1000) documents in one call. Each BulkAnchorInput row provides exactly one of fingerprint (a pre-computed 64-char hex SHA-256) or data (raw content, fingerprinted client-side the same way anchor() does it) — you can mix both forms across rows in one call.

from arkova import Arkova, BulkAnchorInput

with Arkova(api_key="ak_live_...") as arkova:
    with open("contract.pdf", "rb") as f:
        contract_bytes = f.read()

    result = arkova.anchor_bulk(
        [
            # Already hashed elsewhere — send the fingerprint directly.
            BulkAnchorInput(fingerprint="a" * 64, external_id="invoice-001"),
            # Raw content — the SDK hashes it for you before it's ever sent.
            BulkAnchorInput(
                data=contract_bytes,
                credential_type="CONTRACT_PRESIGNING",
                document_type="contract",
                matter_or_case_ref="CASE-42",
            ),
        ],
        duplicate_strategy="skip",
        batch_id="nightly-2026-07-28",
    )

    print(result.queued, result.duplicates, result.errors)
    for anchor in result.anchors or []:
        print(anchor.public_id, anchor.status)

Options: dry_run=True validates every row (including dedup checks) without queuing or deducting credits — result.anchors is None on a dry run. duplicate_strategy controls what happens when a fingerprint already exists in-batch or in your org; the server default is "fail" (raises ArkovaError(code="duplicate_fingerprints") on any duplicate — pass "skip", "supersede", or "link" to proceed instead). batch_id is your own correlation ID, echoed back and surfaced in audit events.

Limits: empty input returns a zero-row response immediately, no network call. More than 1000 rows raises ArkovaError(code="batch_too_large") — the SDK does not auto-chunk (splitting a logical batch across requests would let a duplicate fingerprint slip past the cheaper intra-batch check and would deduct credits per chunk instead of atomically for the whole batch; split manually and correlate with a shared batch_id if you need more than 1000 rows). A row with neither fingerprint nor data (or with both) raises ArkovaError(code="invalid_request"), checked before any network call.

Verify a fingerprint

from arkova import Arkova

fingerprint = "a" * 64

with Arkova(api_key="ak_live_...") as arkova:
    result = arkova.verify_fingerprint(fingerprint)
    print(result.verified, result.public_id)

Verify a public ID

from arkova import Arkova

with Arkova(api_key="ak_live_...") as arkova:
    result = arkova.verify("ARK-2026-ABC")
    print(result.verified, result.description, result.confidence_scores)

Upgrade from 2.2.0. In 2.2.0 verify() raised ArkovaError: Arkova API returned an unexpected response shape on any record carrying compliance controls — compliance_controls was typed as a mapping, but the API returns a list of control-ID strings. Fixed in 2.3.0 (2.2.1 was never published — see CHANGELOG.md); there is no workaround on 2.2.0 short of bypassing the model layer.

verify() returns the rich v1 verification shape, including API-RICH-01 fields such as compliance_controls (a list of control-ID strings, accompanied by compliance_controls_note whenever present), chain_confirmations, parent_public_id, version_number, file_mime, and file_size, plus API-RICH-02 fields confidence_scores and sub_type, and fingerprint_source / proof_availability / proof_availability_note, when the API response includes them. The same optional rich fields are typed on v2 verify_fingerprint() and get_anchor() responses, so newer API payloads are not silently hidden by the SDK model layer.

Async client

import asyncio
from arkova import AsyncArkova


async def main() -> None:
    async with AsyncArkova(api_key="ak_live_...") as arkova:
        orgs = await arkova.list_orgs()
        print([org.display_name for org in orgs.organizations])


asyncio.run(main())

Errors and retries

ArkovaError preserves the API v2 RFC 7807 problem document and the Retry-After header when present. code carries the machine-readable error code — from the plain-JSON error field on v1 write-path errors (anchor() / anchor_bulk() codes include "insufficient_credits", "duplicate_fingerprints", "batch_too_large", "invalid_request"), or the RFC 7807 type slug on v2 problem documents.

from arkova import Arkova, ArkovaError

try:
    with Arkova(api_key="ak_live_...") as arkova:
        arkova.get_anchor("ARK-DOC-MISSING")
except ArkovaError as exc:
    print(exc.status_code, exc.code, exc.problem.type if exc.problem else None)

The client retries 429 and 5xx responses by default and respects Retry-After within max_retry_delay (seconds, default 5). Longer server waits return the original error without an early retry. Pass retries=0 to disable retries.

Offline proof verification (no network, no API key)

verify_bundle verifies an exported Arkova proof package entirely offline — an independent re-derivation of the documented bundle format (Merkle recompute with the CVE-2012-2459 structural guard, fixed-offset on-chain payload decode, 80-byte header rules, timestamp honesty, Ed25519 signed bundles). It makes zero network calls and never contacts Arkova; on-chain confirmation runs only against canned or caller-supplied independent-node responses.

import json
from arkova import verify_bundle

packet = json.load(open("proof.json"))
outcome = verify_bundle(packet)          # recompute-only
print(outcome.verdict, outcome.reason_code)  # "VERIFIED" / None, or a frozen code

Every NOT-VERIFIED outcome carries one frozen machine reason code (arkova.REASON_CODES), kept in lockstep with the TypeScript reference verifier via a cross-runtime parity gate in the Arkova repo. A passing signature never substitutes for the cryptographic recompute; a failing explicitly-requested signature check fails the verdict closed.

Agent lifecycle

Arkova and AsyncArkova expose matching register_agent, list_agents, get_agent, update_agent, revoke_agent, create_agent_key, and admit_computeid_agent methods. Configure an organization API key with agents:manage; ComputeID admission accepts API-key authentication only and never widens scopes. Save keys returned by key creation and admission immediately; lifecycle writes are never automatically retried. The restricted key returned by admission is not automatically an agent-management key.

Webhook management (2.7.0 release candidate)

The methods in this section are prepared for arkova 2.7.0 and were not included in 2.6.2. Verify PyPI availability before installing; source metadata is not publication proof. Both Arkova and AsyncArkova provide the same 11 operations:

Method Action
create_webhook(url=..., events=..., description=..., verify=..., scope=...) Register an endpoint; the typed result includes its secret once.
list_webhooks(limit=..., offset=...) List organization endpoints with the server's pagination fields.
get_webhook(endpoint_id) Read one endpoint.
update_webhook(endpoint_id, ...) Update URL, events, description, is_active, or scope.
delete_webhook(endpoint_id) Delete an endpoint; returns None after HTTP 204.
test_webhook(endpoint_id) Send a test; inspect success and status_code.
list_webhook_deliveries(endpoint_id=..., limit=...) Read the newest bounded delivery history.
list_webhook_dead_letters(limit=...) Read unresolved dead letters.
replay_webhook_delivery(delivery_id) Attempt replay; inspect ok and status_code.
resolve_webhook_dead_letter(entry_id) Mark a dead letter resolved.
retry_webhook_materialization(outbox_id, request_id=...) Request agent-outbox recovery using UUID identifiers.

Use an organization API key with webhooks:manage. Every route, including reads, also requires the key creator's current owner or administrator membership in that exact organization. A key's scope alone does not grant that membership. These methods use the existing client base URL and API-key authentication for /api/v1/webhooks; they do not acquire credentials or change permissions.

For registration and updates, scope="self" targets the key's organization; scope="self_and_descendants" explicitly requests its descendant organizations. When you send a scope, the SDK requires the successful response to acknowledge that exact value. Omit scope to preserve the server's registration default or leave an existing endpoint's scope unchanged. An omitted description is also left unchanged on updates; description=None explicitly clears it. Older read responses may omit scope, in which case endpoint.scope is None.

Returned models expose only documented fields. Endpoint reads never expose a secret; save the WebhookEndpointWithSecret.secret from creation securely when it is returned. Delivery history excludes raw payloads, receiver bodies, and error text. Dead letters expose endpoint_display_url, with URL credentials, query parameters, and fragments removed. History and dead-letter limits are 1–100 (default 50); their total is the number of rows returned, not an exhaustive count or a cursor. Attempt number zero is valid.

A replay receipt with replayed=True confirms the replay was attempted, not that the receiver accepted it. Only ok=True with a 2xx status_code reports delivery success. Likewise, resolving a dead letter or receiving a materialization recovery receipt does not prove delivery. Recovery's request_id is an explicit idempotency identifier; retain it when checking the result of an uncertain request.

Webhook operations make one request and are never automatically retried, including when the client has retries configured. Error codes retain known server reasons such as ssrf_blocked, payload_refused, and authorization_unavailable; raw server and transport details are omitted. A timeout, transport failure, or invalid successful mutation response warns that the operation may have succeeded. Inspect its receipt before deciding to retry. These client methods do not change delivery schedules or guarantee delivery.

Retry waits are bounded: max_retry_delay (default 5 seconds, available on sync and async clients). If a valid server Retry-After requests a longer wait, the client surfaces the original error and retry metadata without sleeping or retrying early. Short valid waits retain the server minimum.

Metadata

Release files for arkova 2.7.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 arkova 2.7.0
File Size Uploaded
arkova-2.7.0.tar.gz 132.9 kB Details

Built distribution (wheel)

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

Total release size: 181.7 kB

Release files / arkova-2.7.0.tar.gz

Download URL arkova-2.7.0.tar.gz
Size 132.9 kB
Tags Source
SHA-256 checksum
How to use checksums
304b6531d41e5145fb03f582fd3f421bdcd9a4a360b49eaf4dcdfd84bd90d17c
BLAKE2b-256 checksum
How to use checksums
def839a6ad0bfe75fe4708da53050264e30fc52fb0c50b5d9618d7a8d398d4cf
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 Oct 16, 2026.

Transparency log

Release files / arkova-2.7.0-py3-none-any.whl

Download URL arkova-2.7.0-py3-none-any.whl
Size 48.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d853fe482797b6f36a48ecb3f27f55fdd97c6e57cd8115924755f4a08dc76d61
BLAKE2b-256 checksum
How to use checksums
cc1d26dcff677a971d9a0b826bc2735cf070b18a7ebef4d37f4a4221b71e502b
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 Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.7.0 This release

2 release files

2.6.2

2 release files

2.6.1

2 release files

2.6.0

2 release files

2.4.1

2 release files

2.3.0

2 release files

2.2.0

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