Application-agnostic Holded API client for MisterPato projects.
Project description
misterpato-holded-client
Application-agnostic Python client package for Holded Invoice API v1 and v2.
The package exposes a small synchronous SDK around Holded's v1 and v2 Invoice API. It is intentionally independent of Django, Celery, databases, or any app-specific idempotency layer.
Installation
Install from a checkout in editable mode:
python3 -m pip install -e .
Install with test dependencies for development:
python3 -m pip install -e .[test]
python3 -m pytest -q
Runtime dependency: requests.
Supported Python: >=3.9.
Development environment
A local virtual environment can be created with:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .[dev]
python -m pytest -q
This checkout includes .python-version set to 3.10.12 for pyenv-compatible tooling. The package itself supports Python >=3.9.
Publishing
Build and upload instructions are documented in PUBLISHING.md. Short version:
source .venv/bin/activate
python -m pytest -q
rm -rf dist build *.egg-info src/*.egg-info
python -m build
python -m twine check dist/*
python -m twine upload dist/*
Do not commit real PyPI/TestPyPI tokens. .pypirc.example contains only placeholders.
Basic usage
Choose the Holded API version once at client construction. Resources are wired directly on the client instance for both versions.
v1
from misterpato_holded import HoldedClient, VERSION_1
client = HoldedClient(api_key="holded-api-key", version=VERSION_1)
HoldedClient wires v1 resources directly on the client instance:
- Generic resources:
client.documents,client.contacts,client.treasuries,client.payment_methods - Typed document resources:
client.invoices,client.credit_notes,client.sales_receipts,client.sales_orders,client.proformas,client.waybills,client.estimates,client.purchases,client.purchase_orders,client.purchase_refunds
MVP document resources support the full essential document action set for every document type, especially client.invoices and client.credit_notes: list, iter_all, list_all, create, get, update, approve, pay, delete, get_pdf_base64, and get_pdf.
v2
from misterpato_holded import HoldedClient, VERSION_2
client = HoldedClient(api_key="holded-api-key", version=VERSION_2)
v2 currently wires only:
client.contactsclient.invoicesclient.credit_notesclient.proformasclient.treasuries
Other v2 document types (purchases and so on) are not implemented yet. Use v1 if you need those resources today.
v2 invoice actions implemented today: list, iter_all, list_all, create, get, update, approve, bulk_approve, pay, delete, get_pdf, and get_pdf_base64.
v2 credit note actions implemented today: list, iter_all, list_all, create, get, update, approve, pay, delete, get_pdf, and get_pdf_base64.
v2 proforma actions implemented today: list, iter_all, list_all, create, get, update, approve, delete, get_pdf, and get_pdf_base64 (no pay — proformas have no payments in the Holded v2 API).
v2 contacts support list, iter_all, list_all, create, get, update, and delete.
v2 treasury account actions implemented today: list, iter_all, list_all, create, and get (Holded documents no update, delete, or archive endpoint for treasury accounts).
Configuration
client = HoldedClient(
api_key="holded-api-key",
version=VERSION_1, # or VERSION_2
base_url="https://api.holded.com/api",
timeout=30,
timezone="Europe/Madrid", # v1 only; ignored for v2 wiring
)
Notes:
- Blank API keys raise
HoldedConfigError. - Unknown
versionvalues raiseHoldedConfigError. - v1 auth: the client sends
key: <api_key>automatically. - v2 auth: the client sends
Authorization: Bearer <api_key>automatically and does not send the v1keyheader. - URLs: both versions use the same default
base_url(https://api.holded.com/api). v2 resource paths are/v2/...(for example/v2/invoices,/v2/contacts). You do not need a special v2base_urlfor normal use. - v1 timezone:
timezonecontrols how naivedateanddatetimevalues are serialized to Holded Unix timestamps. v2 date serialization uses ISO/date strings instead (see below).
Holded API v2
v2 is a separate surface under misterpato_holded.v2 with its own dataclasses, pagination, date formats, and invoice status vocabulary. v1 behavior is unchanged.
v2 models and .raw
Import v2 models from misterpato_holded.v2.models (or misterpato_holded.v2 for the public subset):
from misterpato_holded.v2.models import (
CreateContactRequest,
CreateInvoiceRequest,
Invoice,
InvoiceStatus,
PayInvoiceRequest,
UpdateContactRequest,
)
v2 response dataclasses are separate from v1 models. Every parsed response preserves the original decoded payload on .raw.
v2 invoice listing and InvoiceStatus
v2 invoice listing uses cursor pagination and a native status= filter. Supported values are validated locally before HTTP:
from misterpato_holded.v2.models import InvoiceStatus
pending = client.invoices.list(status=InvoiceStatus.PENDING)
# or: client.invoices.list(status="pending")
Allowed status values: pending, completed, partial, cancelled, failed, overdue.
v2 does not support v1's boolean paid= filter. Passing paid= raises TypeError because the argument is not part of the v2 API. Use explicit status= values instead.
v2 list methods also do not accept v1's page= argument. Use cursor= on list() or iter_all() / list_all() for multi-page reads.
v2 invoice creation
Create with an existing contact id:
from datetime import date
created = client.invoices.create({
"contact_id": "contact-id",
"date": date(2026, 6, 29),
"items": [{"name": "Service", "units": 1, "subtotal": "120.00"}],
})
print(created.id, created.raw)
Or supply contact creation fields when you do not have a contact_id. The client creates the contact first, then creates the invoice with the returned id. It does not search existing contacts first:
created = client.invoices.create({
"contactName": "Cliente Demo",
"contactEmail": "cliente@example.com",
"date": "2026-06-29",
"items": [{"name": "Service", "units": 1, "subtotal": "120.00"}],
})
Recognized contact auto-creation aliases: contactName / contact_name and contactEmail / contact_email. The caller payload is copied internally; your original mapping is not mutated.
Contact auto-creation requires both contact-write and invoice-write permissions on the Holded API key. If contact creation succeeds but invoice creation fails, the new contact remains in Holded; callers that need idempotency must handle that at the application layer.
v2 invoice create does not emulate v1's approve=True. Passing approve=True raises HoldedRequestValidationError locally with no HTTP request. Approve invoices explicitly after creation:
created = client.invoices.create({...})
approved = client.invoices.approve(created.id)
Create responses usually return only {id}; call get() when you need the full invoice body.
v2 invoice actions
invoice = client.invoices.get("invoice-id")
updated = client.invoices.update("invoice-id", {"notes": "Updated notes"})
approved = client.invoices.approve("invoice-id")
paid = client.invoices.pay("invoice-id", {"date": "2026-06-29", "amount": "120.00"})
deleted = client.invoices.delete("invoice-id")
pdf_bytes = client.invoices.get_pdf("invoice-id")
pdf_base64 = client.invoices.get_pdf_base64("invoice-id")
update() is documented by Holded as full replacement. Send a complete payload or risk clearing omitted fields.
get_pdf() requests the binary PDF endpoint and returns raw bytes. get_pdf_base64() base64-encodes those bytes locally for compatibility with v1-style callers.
delete() handles Holded's 204 no-content response.
v2 invoice bulk approve
Known Holded-side issue (checked 2026-07-02): the live
/v2/invoices/bulk/approveendpoint currently returns422 {"message": "None of the invoices could be approved"}for every request, including freshly created, individually approvable draft invoices on an account with Verifactu disabled — reproduced with this client, with rawrequests, and withcurl. Until Holded fixes the endpoint,bulk_approve()will raiseHoldedValidationError; approve invoices one by one withapprove()instead. The method is kept because it matches the documented API contract and needs no client change once Holded resolves it.
bulk_approve() posts {"ids": [...]} to /v2/invoices/bulk/approve. Holded processes each invoice independently:
result = client.invoices.bulk_approve(["invoice-id-1", "invoice-id-2"])
print(result.status_code, result.message, result.raw)
204: all invoices approved;messageis empty andrawis{}.207: some invoices could not be approved;messagecarries Holded's explanation andrawpreserves the response body.422(none approved) and other errors raise the usual typed exceptions (HoldedValidationError, ...).
An empty sequence or blank ids raise HoldedRequestValidationError locally with no HTTP request.
v2 credit notes
client.credit_notes targets /v2/credit-notes (Holded's facturas rectificativas) with the same action set as invoices except bulk_approve:
created = client.credit_notes.create({
"contact_id": "contact-id",
"date": date(2026, 6, 29),
"items": [{"name": "Refund", "units": 1, "price": "10.00"}],
})
credit_note = client.credit_notes.get(created.id)
approved = client.credit_notes.approve(created.id)
paid = client.credit_notes.pay(created.id, {"amount": "10.00", "date": "2026-06-29"})
pdf_bytes = client.credit_notes.get_pdf(created.id)
deleted = client.credit_notes.delete(created.id)
Differences from invoices:
- No contact auto-creation.
create()requires an existingcontact_id;contactName/contactEmailaliases are not recognized, and a payload withoutcontact_idraisesHoldedRequestValidationErrorlocally with no HTTP request. - Mapping payloads only. There are no typed request dataclasses for credit notes; pass plain mappings. Dates and money fields are serialized with the same ISO/Decimal rules as invoices.
- List filters are
limit,cursor,contact_id,status,start_date,end_date,sort, andapproval_status. The invoice-onlydue_date_start,due_date_end, andaccounting_account_numfilters are not accepted (TypeError). Status values reuse the invoice vocabulary (pending,completed,partial,cancelled,failed,overdue).
Responses parse into CreditNote and the generic CreateDocumentResponse / UpdateDocumentResponse / ApproveDocumentResponse / PayDocumentResponse / DeleteDocumentResponse dataclasses, all preserving .raw.
v2 proformas
client.proformas targets /v2/proformas with the same action set as credit notes minus pay — proformas carry no payment fields in the Holded v2 API, so there is no pay method at all:
created = client.proformas.create({
"contact_id": "contact-id",
"date": date(2026, 6, 29),
"items": [{"name": "Line", "units": 1, "price": "10.00"}],
})
proforma = client.proformas.get(created.id)
approved = client.proformas.approve(created.id)
pdf_bytes = client.proformas.get_pdf(created.id)
deleted = client.proformas.delete(created.id)
Proforma create() supports the same contact auto-creation as invoices: when the payload has no contact_id but includes contactName / contact_name (optionally contactEmail / contact_email), the contact is created first via POST /v2/contacts, the returned id is used as contact_id, and the alias keys are stripped from the proforma payload. There is no contact search or deduplication — an existing contact with the same name is not reused. Without contact_id and without alias fields, create() raises HoldedRequestValidationError locally with no HTTP request:
created = client.proformas.create({
"contactName": "New Client SL",
"contactEmail": "billing@newclient.example",
"items": [{"name": "Line", "units": 1, "price": "10.00"}],
})
List filters are limit, cursor, contact_id, status, start_date, end_date, and sort — no approval_status and no due-date filters (TypeError). Payloads are plain mappings, serialized with the same ISO/Decimal rules as invoices. Responses parse into Proforma (no payment fields) and the same generic document response dataclasses, all preserving .raw.
v2 treasuries
client.treasuries targets /v2/treasury/accounts. Holded documents only create, list, and get — there is no update, delete, or archive endpoint, so the resource has no such methods:
created = client.treasuries.create({"name": "Main bank", "type": "bank", "currency": "EUR"})
account = client.treasuries.get(created.id)
print(account.balance) # decimal string, e.g. "1250.55" — parse with Decimal in your app
banks = client.treasuries.list(type="bank", archived=False)
for account in client.treasuries.iter_all():
print(account.id, account.name, account.type)
create() validates locally (no HTTP) that name is present and type is one of bank, card, gateway, or cash. List filters are cursor, limit, type (same allowed values), and archived — archived=True returns only archived accounts, archived=False only active ones (sent as lowercase true/false on the wire), and omitting it returns both. Accounts parse into Treasury with balance kept as a decimal string (no float conversion) and .raw preserved; create() returns the generic CreateDocumentResponse.
v2 contacts
from misterpato_holded.v2.models import CreateContactRequest, UpdateContactRequest
contact = client.contacts.create(CreateContactRequest(
name="Cliente Demo",
email="cliente@example.com",
))
same_contact = client.contacts.get(contact.id)
updated = client.contacts.update(contact.id, UpdateContactRequest(name="Cliente Demo", email="cliente@example.com"))
deleted = client.contacts.delete(contact.id)
Raw mapping payloads are also supported. Contact update() is full replacement, same caveat as invoices.
Contact list() parses the v2 cursor envelope (items, has_more, cursor). iter_all() / list_all() follow cursors with the same semantics as invoices, preserving filters on every page:
for contact in client.contacts.iter_all(limit=100):
print(contact.id, contact.name)
all_matching = client.contacts.list_all(email="cliente@example.com", max_pages=20)
v2 pagination
v2 invoice pagination is cursor-based, not page-number based:
first_page = client.invoices.list(status="pending", limit=50)
for invoice in client.invoices.iter_all(status="pending", start_date="2026-01-01"):
print(invoice.id, invoice.document_number, invoice.raw)
all_pending = client.invoices.list_all(status="pending", max_pages=20)
iter_all() follows cursor values until has_more is false. Filters passed to the first call are preserved on later cursor requests. If max_pages is reached before pagination finishes, HoldedPaginationLimitError is raised instead of silently returning partial data.
For a single page, pass cursor= to list() directly.
v2 date and money serialization
v2 sends ISO date strings (for example 2026-06-29) rather than v1 Unix timestamp strings. List date filters reject Unix timestamp ints or numeric strings.
Money values still use Decimal(str(amount)), are quantized to 0.01 with ROUND_HALF_UP, then sent as JSON numbers.
client.invoices.create({
"contact_id": "contact-id",
"date": date(2026, 6, 29),
"items": [{"name": "Service", "units": 1, "subtotal": "10.235"}], # sends 10.24
})
Migrating from v1 to v2
| Topic | v1 | v2 |
|---|---|---|
| Client init | version=VERSION_1 |
version=VERSION_2 |
| Auth header | key: <api_key> |
Authorization: Bearer <api_key> |
| Resources | documents, contacts, treasuries, payment methods, all document types | contacts, invoices, credit notes, proformas, and treasuries only |
| Models | misterpato_holded.v1.models |
misterpato_holded.v2.models |
| Invoice paid filter | paid=True/False |
not supported; use status= |
| Pagination | page=N, empty page stops |
cursor, has_more envelope |
| Dates on wire | Unix timestamp strings | ISO/date strings |
| Create + approve | create(..., approve=True) |
create, then explicit approve() |
| Contact on create | inline contact_name on document payload |
contact_id or auto-create via contact fields |
| base64 JSON field and/or binary helper | binary PDF bytes; base64 helper encodes locally |
Webhooks
misterpato_holded.webhooks provides framework-agnostic helpers for receiving Holded webhooks: HMAC-SHA256 signature verification, header parsing, typed payload dataclasses, and event-name constants. There is no HTTP server, routing, or persistence — plug the helpers into whatever framework the app uses.
from misterpato_holded import webhooks
from misterpato_holded.exceptions import HoldedWebhookSignatureError
def receive(raw_body: bytes, request_headers: dict, secret: str):
try:
event = webhooks.parse_event(raw_body, request_headers, secret)
except HoldedWebhookSignatureError:
return 401 # reject: missing or invalid signature
if event.event in webhooks.DOCUMENT_EVENTS:
handle_document(event.payload, dedupe_key=event.idempotency_key)
elif event.event in webhooks.CONTACT_EVENTS:
handle_contact(event.payload, dedupe_key=event.idempotency_key)
# unknown events arrive with event.payload = None; ignore or log them
return 204
Notes:
parse_eventverifies the signature against the exact raw body bytes before parsing anything, and raisesHoldedWebhookSignatureErroron failure. Always pass the raw bytes as received, never re-serialized JSON.- Header lookup is case-insensitive; all
x-holded-webhook-*metadata is exposed on the returnedHoldedWebhookEvent(event,webhook_id,account_id,delivered_at,webhook_version). - Delivery is at-least-once: deduplicate on
event.idempotency_key((account_id, webhook_id)). Dedup storage, queuing, and retry handling are the consuming app's responsibility. - Payload dataclasses (
WebhookDocumentPayload,WebhookDeletedDocumentPayload,WebhookContactPayload,WebhookDeletedContactPayload) keep nullable fields asNone, keeptotal/subtotalas strings (parse withDecimal), and preserve the original payload on.raw. webhooks.verify_signature(raw_body, signature_header, secret)is available standalone when you only need the boolean check.
The full local reference for events, headers, payload schemas, and receiver guidance is docs/holded-v2-documentation/webhooks.md.
Documents (v1)
Create an invoice with the typed resource:
from datetime import date
from misterpato_holded import HoldedClient, VERSION_1
from misterpato_holded.v1.models import CreateDocumentRequest, DocumentItem
client = HoldedClient(api_key="holded-api-key", version=VERSION_1)
invoice = client.invoices.create(CreateDocumentRequest(
date=date(2026, 1, 15),
contact_name="Cliente Demo",
currency="EUR",
items=[DocumentItem(name="Service", units=1, subtotal="120.00")],
))
print(invoice.id, invoice.doc_number, invoice.raw)
The same operation can be done through the generic documents resource:
from misterpato_holded.v1.documents import DocumentType
invoice = client.documents.create(
DocumentType.INVOICE,
CreateDocumentRequest(date=date.today(), contact_name="Cliente Demo"),
)
Pass approve=True to approve a document during creation, which sends Holded's
approveDoc: true field. Pass custom_fields=[...] to send Holded's
customFields array:
custom_fields = [{"field": "project", "value": "MisterPato"}]
proforma = client.proformas.create(
CreateDocumentRequest(date=date.today(), contact_name="Cliente Demo"),
approve=True,
custom_fields=custom_fields,
)
Other document operations:
document = client.invoices.get("document-id")
updated = client.invoices.update("document-id", {"notes": "Updated notes"})
paid = client.invoices.pay("document-id", {"date": date.today(), "amount": "120.00"})
approved = client.invoices.approve("document-id")
pdf_base64 = client.invoices.get_pdf_base64("document-id")
pdf_bytes = client.invoices.get_pdf("document-id")
deleted = client.invoices.delete("document-id")
Raw mappings are accepted for pragmatic use. Known money and timestamp fields are serialized before sending, and locally known required fields are validated before the HTTP request.
Contacts (v1)
from misterpato_holded.v1.models import CreateContactRequest, UpdateContactRequest
contact = client.contacts.create(CreateContactRequest(
name="Cliente Demo",
email="cliente@example.com",
))
same_contact = client.contacts.get(contact.id)
updated_contact = client.contacts.update(contact.id, UpdateContactRequest(phone="+34 600 000 000"))
deleted = client.contacts.delete(contact.id)
Raw mapping payloads are also supported:
contact = client.contacts.create({"name": "Cliente Demo", "email": "cliente@example.com"})
Treasuries and payment methods (v1)
These resources are list-only in this v1 package:
treasuries = client.treasuries.list()
payment_methods = client.payment_methods.list()
Pagination (v1)
List resources support one-page list(page=N), lazy iter_all(...), and materialized list_all(...).
page_2 = client.invoices.list(page=2)
for invoice in client.invoices.iter_all(starttmp=date(2026, 1, 1), endtmp=date(2026, 1, 31)):
print(invoice.id, invoice.doc_number)
all_contacts = client.contacts.list_all(max_pages=20)
iter_all() starts at page 1 by default and stops when Holded returns an empty list. If max_pages is reached before an empty page is observed, HoldedPaginationLimitError is raised instead of silently returning partial data.
Date and datetime serialization (v1)
Holded v1 expects string Unix timestamps for document/payment dates and starttmp/endtmp filters. This package accepts int, str, date, and datetime values and emits canonical decimal strings on the wire.
Rules:
- Default timezone for naive
date/datetime:Europe/Madrid. - Configure it with
HoldedClient(..., timezone="UTC")or another IANA timezone name. starttmp=date(...)uses local00:00:00.endtmp=date(...)uses local23:59:59.- Naive
datetimeis interpreted in the client timezone. - Aware
datetimepreserves its instant.
from datetime import date, datetime, timezone
client = HoldedClient(api_key="holded-api-key", timezone="Europe/Madrid")
client.invoices.list(starttmp=date(2026, 1, 1), endtmp=date(2026, 1, 31))
client.invoices.create(CreateDocumentRequest(date=datetime(2026, 1, 15, 10, 30), contact_name="Demo"))
client.invoices.pay("document-id", {"date": datetime.now(timezone.utc), "amount": "120.00"})
Money serialization
Money values use Decimal(str(amount)), are quantized to 0.01 with ROUND_HALF_UP, then sent as JSON numbers.
DocumentItem(name="Service", units=1, subtotal="10.235") # sends 10.24
Error handling
All package exceptions inherit from HoldedError.
from misterpato_holded.exceptions import (
HoldedAuthError,
HoldedConfigError,
HoldedError,
HoldedNotFoundError,
HoldedPaginationLimitError,
HoldedRequestValidationError,
HoldedServerError,
HoldedValidationError,
)
try:
invoice = client.invoices.create(CreateDocumentRequest(contact_name="Missing date"))
except HoldedRequestValidationError:
# Local validation failed before any HTTP request was sent.
raise
except HoldedValidationError as exc:
# Holded returned a remote 400/422 validation error.
print(exc.status_code, exc.response_json)
except HoldedAuthError:
print("Invalid or unauthorized Holded API key")
except HoldedNotFoundError:
print("Document/contact not found")
except HoldedServerError as exc:
if exc.retryable:
print("Holded server error; safe for caller-controlled retry policy")
except HoldedPaginationLimitError:
print("Pagination reached max_pages before an empty page")
except HoldedConfigError:
print("Invalid local client configuration")
except HoldedError as exc:
print(f"Holded client failure: {exc}")
Status mapping:
400and422:HoldedValidationError401and403:HoldedAuthError404:HoldedNotFoundError409:HoldedConflictError5xx:HoldedServerErrorwithretryable=True- Invalid successful JSON or unexpected response shape:
HoldedResponseError
Live v2 tests
The live suite under tests/live/ hits the real Holded v2 API at api.holded.com. It is
opt-in only: default python -m pytest -q does not collect or run these tests.
Only human developers run the live suite (unless he explicitly delegates otherwise). Agents diagnose
from shared logs and verify with the mocked suite only (python -m pytest -q).
export HOLDED_V2_API_KEY="your-v2-api-key"
pytest tests/live > live.log 2>&1
If a live run fails, share live.log with an agent — never the API key. The agent edits
code/tests and re-verifies with python -m pytest -q; the human developer re-runs pytest tests/live.
Without HOLDED_V2_API_KEY exported, pytest tests/live skips every test with a clear
reason and makes no network calls.
Cleanup caveat: an interrupted run (Ctrl-C, crash) may leave live-test- contacts or
invoices in the Holded account. Identify them by the live-test- prefix and delete manually
in the Holded UI.
Residual risk: invoice lifecycle tests that call pay() may leave a real payment record
on the account even after the throwaway invoice is deleted.
Development verification
python3 -m pytest -q
python3 -m compileall -q src tests
python3 - <<'PY'
from misterpato_holded import HoldedClient, VERSION_1, VERSION_2
v1 = HoldedClient(api_key="x", version=VERSION_1)
print(v1.version, v1.invoices.doc_type.value)
v2 = HoldedClient(api_key="x", version=VERSION_2)
print(v2.version, type(v2.invoices).__name__, type(v2.contacts).__name__)
PY
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file misterpato_holded_client-0.2.4.tar.gz.
File metadata
- Download URL: misterpato_holded_client-0.2.4.tar.gz
- Upload date:
- Size: 44.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d60148deb82e91129f783977d52059b6dca2afb643ea989e6d774cc6cf2c8a93
|
|
| MD5 |
1dac305b52afb1b26638c04aa38e1114
|
|
| BLAKE2b-256 |
e1ac80bb6c43839816b18e576f25fbd2168eef0fb610c1d0336e7395b4091dda
|
File details
Details for the file misterpato_holded_client-0.2.4-py3-none-any.whl.
File metadata
- Download URL: misterpato_holded_client-0.2.4-py3-none-any.whl
- Upload date:
- Size: 40.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58f36c660ab9416249d8fddae3097aa292f84ac3a6205c059ab03ab82b6216c8
|
|
| MD5 |
c6c2f0746113bc0f81237b86debffd12
|
|
| BLAKE2b-256 |
f29a260ee739ab9e4e0ee0df2416e27068f427e414ad2c952db40f481ea83e61
|