wfirma-sdk-python
Unofficial, minimal Python SDK for the wFirma.pl API (API Key & OAuth2).
Status: prototype for integrations. Before production, verify scopes, payloads and responses with official docs.
This SDK focuses on a pragmatic subset: contractors, invoices (get/add/download/send), and company accounts. It exposes a low-level
call()escape hatch for everything else.
Install
pip install wfirma-sdk-python # when published
poetry add wfirma-sdk-python
Python >= 3.9 recommended.
Quickstart
from wfirma_sdk import WFirmaAPIClient
client = WFirmaAPIClient(
base_url="https://api2.wfirma.pl",
company_id="YOUR_COMPANY_ID", # optional but recommended
# ---- Auth option A: API Key headers ----
access_key="...",
secret_key="...",
app_key="...",
# ---- OR Auth option B: OAuth2 ----
# oauth2_token="eyJhbGciOi..." # Bearer token
)
# Add contractor
resp = client.contractors.add({
"name": "Nazwa kontrahenta",
"zip": "12-345",
"country": "PL",
"tax_id_type": "custom",
"nip": "1111111111",
})
print(resp)
# Get invoice
inv = client.invoices.get(12345678)
print(inv)
# Download invoice PDF link (valid for a short time on backend)
link = client.invoices.download(
12345678,
page="all",
address=0,
leaflet=0,
duplicate=0,
payment_cashbox_documents=0,
warehouse_documents=0,
)
print(link["status"]["code"], link.get("response"))
# Send invoice by email
client.invoices.send(
12345678,
email="odbiorca@adres.pl",
subject="Otrzymałeś fakturę",
page="invoice",
leaflet=0,
duplicate=0,
body="Przesyłam fakturę",
)
Running Examples
To run the demo example:
# Clone the repository
git clone https://github.com/musictechlab/mtl-wfirma-python-sdk.git
cd mtl-wfirma-python-sdk
# Install dependencies with poetry
poetry install
# Set up your environment variables
cp .env.example .env # if available, or create .env manually
# Edit .env with your credentials:
# WFIRMA_OAUTH_TOKEN=your_oauth_token
# WFIRMA_COMPANY_ID=your_company_id
# Run the demo
poetry run python examples/demo.py
The demo will fetch the 20 most recent invoices from your wFirma account.
Authentication
API Key (headers)
Place keys in headers (client does it for you):
accessKeysecretKeyappKey
client = WFirmaAPIClient(
company_id="...",
access_key="...",
secret_key="...",
app_key="...",
)
OAuth2 (Bearer)
Use Authorization Code flow in your app to obtain an access_token. Pass it to the client:
client = WFirmaAPIClient(
company_id="...",
oauth2_token="ACCESS_TOKEN_VALUE", # adds Authorization: Bearer ... and ?oauth_version=2
)
Tip: Regardless of auth method, remember
company_idif your account has multiple companies.
Modules covered
contractors—add,get,edit,findinvoices—get,add,download,send,find(custom XML)company_accounts—find,getcall(path, ...)— low-level escape hatch
Usage
Contractors
# add
client.contractors.add({
"name": "ACME Sp. z o.o.",
"zip": "00-000",
"city": "Warszawa",
"country": "PL",
"tax_id_type": "nip",
"nip": "1234567890",
})
# get
client.contractors.get(12345)
# edit
client.contractors.edit(12345, {"name": "ACME SA", "zip": "00-001"})
# find (basic paging/fields)
client.contractors.find(page=1, limit=50, fields=["Contractor.id", "Contractor.name"])
Advanced find — pass your own <parameters> as XML:
parameters_xml = b"""<?xml version="1.0" encoding="UTF-8"?>
<api>
<contractors>
<parameters>
<page>1</page>
<limit>50</limit>
<fields>
<field>Contractor.id</field>
<field>Contractor.name</field>
</fields>
<conditions>
<condition>
<field>name</field>
<operator>like</operator>
<value>ACME</value>
</condition>
</conditions>
<order>
<asc>name</asc>
</order>
</parameters>
</contractors>
</api>"""
client.contractors.find(conditions_xml=parameters_xml)
Invoices
# get
client.invoices.get(12345678)
add — for complex nested structures use raw XML from wFirma docs:
xml_body = b"""<api>
<invoices>
<invoice>
<contractor>
<name>Testowy kontrahent</name>
<zip>10-100</zip>
<city>Wrocław</city>
<street>Prosta</street>
</contractor>
<type>correction</type>
<parent_id>16679047</parent_id>
<invoicecontents>
<invoicecontent>
<parent_id>19630727</parent_id>
<name>produkt1</name>
<count>1.0000</count>
<price>11.00</price>
</invoicecontent>
<invoicecontent>
<parent_id>19630791</parent_id>
<name>produkt2</name>
<count>1.0000</count>
<price>11.00</price>
</invoicecontent>
<invoicecontent>
<name>nowy - produkt3</name>
<count>1.0000</count>
<price>11.00</price>
</invoicecontent>
</invoicecontents>
</invoice>
</invoices>
</api>"""
client.invoices.add(invoice_xml_body=xml_body)
download — build <parameters> via helper:
client.invoices.download(
12345678,
page="all", address=0, leaflet=0, duplicate=0,
payment_cashbox_documents=0, warehouse_documents=0,
)
send
client.invoices.send(
12345678,
email="odbiorca@adresmailowy123.pl",
subject="Otrzymałeś fakturę",
page="invoice",
leaflet=0,
duplicate=0,
body="Przesyłam fakturę",
)
find — pass your own <parameters> XML for full control:
invoices_find_xml = b"""<?xml version="1.0" encoding="UTF-8"?>
<api>
<invoices>
<parameters>
<page>1</page>
<limit>20</limit>
<fields>
<field>Invoice.id</field>
<field>Invoice.fullnumber</field>
<field>Invoice.date</field>
<field>InvoiceContent.name</field>
<field>InvoiceContent.price</field>
</fields>
<conditions>
<condition>
<field>Invoice.remaining</field>
<operator>gt</operator>
<value>0</value>
</condition>
</conditions>
<order>
<desc>date</desc>
</order>
</parameters>
</invoices>
</api>"""
client.invoices.find(parameters_xml=invoices_find_xml)
Company accounts
client.company_accounts.find()
client.company_accounts.get(999)
Errors
- Network/HTTP/XML parsing errors ⇒ raises
WFirmaAPIError. - API-level status checking: if
<status><code>...</code></status>is notOK(orNO_CONTENT),WFirmaAPIErroris raised with the decoded response for inspection.
from wfirma_sdk import WFirmaAPIError
try:
client.invoices.get("bad-id")
except WFirmaAPIError as e:
print(e, e.http_status, e.response)
Tips & gotchas
- Format: SDK defaults to
inputFormat=xml&outputFormat=xmland parses XML to Python dicts. - Multiple companies: set
company_idin the client to avoid accidental requests to the wrong company. - Temporary links:
/invoices/downloadreturns a link that is valid only briefly (per backend rules). - Rate limits: avoid bursts; consider backoff/retries on
TOTAL REQUESTS LIMIT EXCEEDEDorTOTAL EXECUTION TIME LIMIT EXCEEDED. - Escape hatch:
client.call(path, method=..., params=..., body_xml=...)to hit any endpoint not yet wrapped.
Versioning
This SDK is intentionally small. Breaking changes may occur until 1.0. Pin versions in production.
Contributing
Issues / PRs welcome. Keep the scope minimal and docs-linked.
🪪 License
MIT License — © 2025 MusicTech Lab
Built with ❤️ by MusicTech Lab.
Metadata
Release files for mtl-wfirma-python-sdk 0.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mtl_wfirma_python_sdk-0.0.6.tar.gz | 8.1 kB | Without provenance | Details for mtl_wfirma_python_sdk-0.0.6.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mtl_wfirma_python_sdk-0.0.6-py3-none-any.whl | Python 3 | none | any | Details for mtl_wfirma_python_sdk-0.0.6-py3-none-any.whl |
Total release size: 18.7 kB
Release files / mtl_wfirma_python_sdk-0.0.6.tar.gz
| Download URL | mtl_wfirma_python_sdk-0.0.6.tar.gz |
|---|---|
| Size | 8.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
574d2b5f0b6b800723b5d353c043789c70e84e1dea6eeee1293af67000cf4319
|
|
BLAKE2b-256 checksum How to use checksums |
d4a569bb9fdf8b88365ecb66ed4666829f4d372ec56b237ab4f866b4debfcc6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Release files / mtl_wfirma_python_sdk-0.0.6-py3-none-any.whl
| Download URL | mtl_wfirma_python_sdk-0.0.6-py3-none-any.whl |
|---|---|
| Size | 10.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
64101ffab2f224fe46a112371210bc9efb4afbde640ec3543ff6aeecb4fc089a
|
|
BLAKE2b-256 checksum How to use checksums |
a24d6835cc0ac4cda463b701fa63ac5f07b9a4cf10813be2c026262a4460b4c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Security for mtl-wfirma-python-sdk 0.0.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.