exportflow 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,145 @@
1
+ # ExportFlow — .gitignore
2
+ #
3
+ # This is the first commit in the repository, deliberately. Everything after it
4
+ # is safe by construction; doing it second would mean the first commit is the
5
+ # one that has to be audited.
6
+ #
7
+ # Banned from version control by standing rule (Foundation 1.6):
8
+ # secrets, databases, build artifacts, virtualenvs.
9
+
10
+ # --- Secrets -----------------------------------------------------------------
11
+ # .env.example is the one exception: the README's clean-machine bootstrap
12
+ # depends on it, so it must be committed.
13
+ .env
14
+ .env.*
15
+ !.env.example
16
+ .envrc
17
+ *.pem
18
+ *.key
19
+ *.p12
20
+ *.pfx
21
+ id_rsa*
22
+ credentials.json
23
+ service-account*.json
24
+
25
+ # --- Databases ---------------------------------------------------------------
26
+ *.db
27
+ *.sqlite
28
+ *.sqlite3
29
+ *.sqlite3-journal
30
+ *.dump
31
+ *.sql.gz
32
+ pgdata/
33
+
34
+ # --- Python ------------------------------------------------------------------
35
+ __pycache__/
36
+ *.py[cod]
37
+ *$py.class
38
+ *.so
39
+ .Python
40
+ build/
41
+ develop-eggs/
42
+ dist/
43
+ eggs/
44
+ .eggs/
45
+ sdist/
46
+ wheels/
47
+ share/python-wheels/
48
+ *.egg-info/
49
+ *.egg
50
+ MANIFEST
51
+
52
+ # --- Virtualenvs -------------------------------------------------------------
53
+ .venv/
54
+ venv/
55
+ ENV/
56
+ env/
57
+ .python-version.local
58
+
59
+ .ocr-check/
60
+
61
+ # --- Tooling caches ----------------------------------------------------------
62
+ .pytest_cache/
63
+ .mypy_cache/
64
+ .ruff_cache/
65
+ .hypothesis/
66
+ .import_linter_cache/
67
+ .coverage
68
+ .coverage.*
69
+ coverage.xml
70
+ htmlcov/
71
+ .tox/
72
+ .nox/
73
+
74
+ # --- Editors and OS ----------------------------------------------------------
75
+ .DS_Store
76
+ .DS_Store?
77
+ ._*
78
+ .Spotlight-V100
79
+ .Trashes
80
+ Thumbs.db
81
+ .idea/
82
+ .vscode/
83
+ *.swp
84
+ *.swo
85
+ *~
86
+
87
+ # --- Real customer documents -------------------------------------------------
88
+ # UNREDACTED ORIGINALS. These are real commercial documents: consignee names,
89
+ # bank references, cargo values. They must never be committed.
90
+ #
91
+ # The brief described this directory as outside the repository. It is not — it
92
+ # sits at the repository root, and the root-anchored /*.pdf rule below does not
93
+ # reach into it, so one `git add -A` would have committed the lot.
94
+ #
95
+ # The redacted corpus lives at api/tests/fixtures/documents/ and is built from
96
+ # these; the originals stay here and stay ignored.
97
+ /sample_docs/
98
+
99
+ # --- Source and rendered documents -------------------------------------------
100
+ # docs/reference/ holds the binaries the written documentation was produced
101
+ # from or rendered to: the source workbooks, and the PDF/zip renderings of
102
+ # docs/*.md. They are inputs and artefacts, not sources, and they are large.
103
+ #
104
+ # NOT a blanket `*.pdf`: the Phase 3 golden corpus at
105
+ # api/tests/fixtures/documents/ is PDFs of real Bills of Lading, and a
106
+ # repository-wide rule would make the first one invisible to `git status`.
107
+ # Foundation 10.3 rates that corpus above any coverage number.
108
+ /docs/reference/
109
+ /*.pdf
110
+ /*.zip
111
+
112
+ # --- Node (web/ and mobile/, from Phase 4) -----------------------------------
113
+ node_modules/
114
+ .next/
115
+ .expo/
116
+ *.tsbuildinfo
117
+
118
+ # Manually acquired source documents. Real regulatory PDFs with real provenance,
119
+ # kept out of git deliberately: they are large, they are republished by their
120
+ # authorities, and their hashes are recorded in regulatory_document instead.
121
+ ops/sources/
122
+
123
+ # The pitch deck and its screenshots. 188 MB of .pptx, .pdf and full-page PNGs,
124
+ # regenerated from the running product rather than authored here, and git keeps
125
+ # every version of a binary for ever. They live on disk beside the docs that
126
+ # describe them; nothing in the build reads them.
127
+ /docs/pitch/
128
+ .vercel
129
+ .env*
130
+
131
+ # App Store Connect API keys
132
+ *.p8
133
+
134
+ # Local agent worktrees and session state
135
+ .claude/
136
+
137
+ # Generated by `make ai-golden` (synthetic, never committed)
138
+ ops/eval/ai_golden/cases/
139
+ ops/eval/ai_golden/manifest.json
140
+ ops/eval/ai-eval-report.json
141
+
142
+ # SDK build output
143
+ sdks/python/dist/
144
+ sdks/typescript/dist/
145
+ sdks/typescript/node_modules/
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to the ExportFlow Python SDK. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the SDK follows
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [1.0.0] - 2026-11-11
8
+
9
+ First release. Speaks version 1 of the ExportFlow API (`/public/v1`).
10
+
11
+ ### Added
12
+ - `ExportFlow` client with `shipments`, `documents`, `records`,
13
+ `verifications`, `usage` and `webhook_endpoints`.
14
+ - `documents.wait` and `verifications.wait` polling helpers.
15
+ - Automatic retries with backoff that reuse the request's idempotency key.
16
+ - Typed errors carrying the API's `code`, HTTP status and request id.
17
+ - `exportflow.webhooks.construct_event` and `verify` for signed deliveries.
18
+ - A check of the API's major version on every response.
19
+ - Detection of a file's media type from its content.
20
+
21
+ ### Compatibility
22
+ - Python 3.9+. API version 1.x.
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.5
2
+ Name: exportflow
3
+ Version: 1.0.0
4
+ Summary: The official Python SDK for the ExportFlow document processing and verification API
5
+ Project-URL: Documentation, https://exportflow.app/developers
6
+ Project-URL: Changelog, https://exportflow.app/developers/changelog
7
+ Author: MeerTech Ltd
8
+ License: Proprietary
9
+ Keywords: documents,erp,exportflow,ocr,trade,verification
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.9
14
+ Requires-Dist: httpx<1,>=0.25
15
+ Description-Content-Type: text/markdown
16
+
17
+ # ExportFlow Python SDK
18
+
19
+ The official Python client for the ExportFlow API: upload trade documents from
20
+ your own software, have them classified and read, confirm what they say, and
21
+ verify them against each other.
22
+
23
+ ```bash
24
+ pip install exportflow
25
+ ```
26
+
27
+ Python 3.9 or later. The only dependency is `httpx`.
28
+
29
+ ## Quick start
30
+
31
+ ```python
32
+ from exportflow import ExportFlow
33
+
34
+ client = ExportFlow(api_key="exfk_test_...") # or set EXPORTFLOW_API_KEY
35
+
36
+ # 1. Upload. The document is scanned, stored and queued for processing.
37
+ document = client.documents.upload(file="bill_of_lading.pdf")
38
+
39
+ # 2. Wait for processing (or use a webhook; see below).
40
+ document = client.documents.wait(document.id)
41
+ print(document.status, document.document_type) # processed bill_of_lading
42
+
43
+ # 3. Read what was extracted.
44
+ extraction = client.documents.extraction(document.id)
45
+ print(extraction.as_dict()) # {"bl_number": "MEDU7741203", ...}
46
+
47
+ # 4. Confirm what the documents say, then verify the shipment.
48
+ client.records.confirm(document.shipment_id, {"port_of_discharge": "NHAVA SHEVA"})
49
+ verification = client.verifications.create(document.shipment_id)
50
+ verification = client.verifications.wait(verification.id)
51
+ for finding in verification.findings:
52
+ print(finding.severity, finding.message)
53
+ ```
54
+
55
+ A key that begins `exfk_test_` works in the **sandbox**: the same pipeline,
56
+ separate data, never charged. A key that begins `exfk_live_` is **production**.
57
+ Create both in the developer console.
58
+
59
+ ## How ExportFlow models documents
60
+
61
+ A **shipment** is a set of documents that travel together: a bill of lading, a
62
+ commercial invoice, a packing list, certificates. Documents are uploaded to a
63
+ shipment, each is **processed** (classified, then its fields extracted), and
64
+ the shipment is **verified** by checking the documents against each other and
65
+ against the published rule set.
66
+
67
+ `client.documents.upload(file=...)` with no `shipment_id` creates a shipment
68
+ for the document. To group several documents, create the shipment first:
69
+
70
+ ```python
71
+ shipment = client.shipments.create(reference="PO-7741", origin_country="NG",
72
+ destination_country="IN")
73
+ for path in ("bl.pdf", "invoice.pdf", "packing_list.pdf"):
74
+ client.documents.upload(file=path, shipment_id=shipment.id)
75
+ ```
76
+
77
+ ### Why you confirm before you verify
78
+
79
+ Verification refuses to start (`ConflictError`, code `not_confirmed`) until the
80
+ shipment's record has been confirmed. This is deliberate. If a field was
81
+ misread, every finding computed from it would be wrong, in both directions.
82
+ Show your user what was read (`client.records.get(shipment_id)`), let them
83
+ confirm or correct it, and send that with `client.records.confirm(...)`.
84
+ Confirm what the document *says*, not what it should say: verification is what
85
+ finds the error.
86
+
87
+ ## Webhooks
88
+
89
+ Polling works; webhooks are better. Register an endpoint once:
90
+
91
+ ```python
92
+ endpoint = client.webhook_endpoints.create(
93
+ "https://erp.example.com/hooks/exportflow",
94
+ event_types=["document.processing.completed", "verification.completed"],
95
+ )
96
+ print(endpoint.signing_secret) # whsec_... store this
97
+ ```
98
+
99
+ Verify every delivery before trusting it:
100
+
101
+ ```python
102
+ from exportflow import webhooks, SignatureVerificationError
103
+
104
+ def handle(request):
105
+ try:
106
+ event = webhooks.construct_event(
107
+ payload=request.body, # raw bytes
108
+ signature=request.headers["ExportFlow-Signature"],
109
+ secret=WEBHOOK_SECRET,
110
+ )
111
+ except SignatureVerificationError:
112
+ return 400
113
+ if already_handled(event.id): # deliveries are at-least-once
114
+ return 200
115
+ if event.type == "document.processing.completed":
116
+ document_id = event.data["document"]["id"]
117
+ ...
118
+ return 200
119
+ ```
120
+
121
+ Answer with any `2xx` within 10 seconds. Anything else is retried with
122
+ increasing delays for about two and a half hours. An event carries identifiers
123
+ and statuses, never document content: fetch the resource to read it.
124
+
125
+ | Event | When |
126
+ |---|---|
127
+ | `document.uploaded` | A document was accepted, scanned and stored |
128
+ | `document.processing.started` | Classification began |
129
+ | `document.processing.completed` | The document was classified and read |
130
+ | `document.processing.failed` | Processing stopped; `GET` the document for why |
131
+ | `verification.completed` | A verification finished; fetch it for the findings |
132
+ | `verification.failed` | A verification could not run |
133
+ | `billing.invoice.generated` | An invoice was issued to your organisation |
134
+
135
+ ## Errors
136
+
137
+ Every error is an `exportflow.ExportFlowError`. One returned by the API is an
138
+ `APIError` with a stable `code`, the HTTP `status_code` and a `request_id` to
139
+ quote to support. Branch on the type or the code, never on the message.
140
+
141
+ ```python
142
+ import exportflow
143
+
144
+ try:
145
+ client.verifications.create(shipment_id)
146
+ except exportflow.ConflictError as error:
147
+ if error.code == "not_confirmed":
148
+ ... # ask the user to confirm the record
149
+ except exportflow.QuotaExceededError:
150
+ ... # the month's allowance is used up
151
+ except exportflow.RateLimitError as error:
152
+ time.sleep(error.retry_after or 5)
153
+ except exportflow.APIConnectionError:
154
+ ... # safe to retry later
155
+ ```
156
+
157
+ | Exception | HTTP | Meaning |
158
+ |---|---|---|
159
+ | `AuthenticationError` | 401 | Key missing, wrong, revoked or expired |
160
+ | `PermissionDeniedError` | 403 | Missing scope, suspended account, network not allowed |
161
+ | `NotFoundError` | 404 | No such resource in this organisation and environment |
162
+ | `ConflictError` | 409 | Not possible in the current state (e.g. `not_confirmed`) |
163
+ | `InvalidRequestError` | 400, 422 | The request is wrong; do not retry |
164
+ | `RateLimitError` | 429 | Slow down; see `retry_after` |
165
+ | `QuotaExceededError` | 429 | Allowance or spending limit reached for the period |
166
+ | `SDKVersionUnsupportedError` | 426 | Upgrade this SDK |
167
+ | `ServerError` | 5xx | ExportFlow's fault; retried automatically |
168
+ | `APIConnectionError`, `APITimeoutError` | none | The API could not be reached |
169
+
170
+ ## Retries and idempotency
171
+
172
+ The client retries connection failures, timeouts, `429 rate_limited` and `5xx`
173
+ up to `max_retries` times (default 2), with backoff, honouring `Retry-After`.
174
+
175
+ Every request that changes something carries an `Idempotency-Key`, and a retry
176
+ sends the *same* key and the *same* body, so the work is done once however many
177
+ times it is sent. To make your own retries safe across process restarts, pass
178
+ your own key:
179
+
180
+ ```python
181
+ client.documents.upload(file="bl.pdf", shipment_id=sid, idempotency_key=f"upload-{erp_document_id}")
182
+ ```
183
+
184
+ Uploading the same file to the same shipment twice returns the first document
185
+ (`duplicate=True`) and is not charged again.
186
+
187
+ ## Usage and charges
188
+
189
+ ```python
190
+ usage = client.usage.get() # this month; or client.usage.get("2026-10")
191
+ for meter in usage.meters:
192
+ print(meter.name, meter.succeeded, "of", meter.quota or "unlimited")
193
+ print(usage.estimated_charge, usage.currency)
194
+ ```
195
+
196
+ Requests are not charged. Usage is recorded when work completes: a document
197
+ accepted, a document processed, a verification completed. Sandbox usage is
198
+ measured and never charged. Charges are estimates until an invoice is issued.
199
+
200
+ ## Configuration
201
+
202
+ ```python
203
+ ExportFlow(
204
+ api_key=None, # default: EXPORTFLOW_API_KEY
205
+ base_url=None, # default: EXPORTFLOW_BASE_URL or https://api.exportflow.app
206
+ timeout=60.0, # seconds per response
207
+ max_retries=2,
208
+ http_client=None, # your own httpx.Client: proxies, TLS, tests
209
+ )
210
+ ```
211
+
212
+ The client is safe to share between threads. Use it as a context manager, or
213
+ call `close()`, to release its connections.
214
+
215
+ ## Versioning
216
+
217
+ This SDK follows semantic versioning and speaks version 1 of the API. Within
218
+ version 1 the API only adds; nothing is removed or changed in meaning, so this
219
+ SDK keeps working as the API grows, and unknown fields are available on every
220
+ model's `.raw`. If the server ever reports a different major version the
221
+ client raises `IncompatibleAPIVersionError` rather than misread a response.
222
+ See `CHANGELOG.md`.
@@ -0,0 +1,206 @@
1
+ # ExportFlow Python SDK
2
+
3
+ The official Python client for the ExportFlow API: upload trade documents from
4
+ your own software, have them classified and read, confirm what they say, and
5
+ verify them against each other.
6
+
7
+ ```bash
8
+ pip install exportflow
9
+ ```
10
+
11
+ Python 3.9 or later. The only dependency is `httpx`.
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ from exportflow import ExportFlow
17
+
18
+ client = ExportFlow(api_key="exfk_test_...") # or set EXPORTFLOW_API_KEY
19
+
20
+ # 1. Upload. The document is scanned, stored and queued for processing.
21
+ document = client.documents.upload(file="bill_of_lading.pdf")
22
+
23
+ # 2. Wait for processing (or use a webhook; see below).
24
+ document = client.documents.wait(document.id)
25
+ print(document.status, document.document_type) # processed bill_of_lading
26
+
27
+ # 3. Read what was extracted.
28
+ extraction = client.documents.extraction(document.id)
29
+ print(extraction.as_dict()) # {"bl_number": "MEDU7741203", ...}
30
+
31
+ # 4. Confirm what the documents say, then verify the shipment.
32
+ client.records.confirm(document.shipment_id, {"port_of_discharge": "NHAVA SHEVA"})
33
+ verification = client.verifications.create(document.shipment_id)
34
+ verification = client.verifications.wait(verification.id)
35
+ for finding in verification.findings:
36
+ print(finding.severity, finding.message)
37
+ ```
38
+
39
+ A key that begins `exfk_test_` works in the **sandbox**: the same pipeline,
40
+ separate data, never charged. A key that begins `exfk_live_` is **production**.
41
+ Create both in the developer console.
42
+
43
+ ## How ExportFlow models documents
44
+
45
+ A **shipment** is a set of documents that travel together: a bill of lading, a
46
+ commercial invoice, a packing list, certificates. Documents are uploaded to a
47
+ shipment, each is **processed** (classified, then its fields extracted), and
48
+ the shipment is **verified** by checking the documents against each other and
49
+ against the published rule set.
50
+
51
+ `client.documents.upload(file=...)` with no `shipment_id` creates a shipment
52
+ for the document. To group several documents, create the shipment first:
53
+
54
+ ```python
55
+ shipment = client.shipments.create(reference="PO-7741", origin_country="NG",
56
+ destination_country="IN")
57
+ for path in ("bl.pdf", "invoice.pdf", "packing_list.pdf"):
58
+ client.documents.upload(file=path, shipment_id=shipment.id)
59
+ ```
60
+
61
+ ### Why you confirm before you verify
62
+
63
+ Verification refuses to start (`ConflictError`, code `not_confirmed`) until the
64
+ shipment's record has been confirmed. This is deliberate. If a field was
65
+ misread, every finding computed from it would be wrong, in both directions.
66
+ Show your user what was read (`client.records.get(shipment_id)`), let them
67
+ confirm or correct it, and send that with `client.records.confirm(...)`.
68
+ Confirm what the document *says*, not what it should say: verification is what
69
+ finds the error.
70
+
71
+ ## Webhooks
72
+
73
+ Polling works; webhooks are better. Register an endpoint once:
74
+
75
+ ```python
76
+ endpoint = client.webhook_endpoints.create(
77
+ "https://erp.example.com/hooks/exportflow",
78
+ event_types=["document.processing.completed", "verification.completed"],
79
+ )
80
+ print(endpoint.signing_secret) # whsec_... store this
81
+ ```
82
+
83
+ Verify every delivery before trusting it:
84
+
85
+ ```python
86
+ from exportflow import webhooks, SignatureVerificationError
87
+
88
+ def handle(request):
89
+ try:
90
+ event = webhooks.construct_event(
91
+ payload=request.body, # raw bytes
92
+ signature=request.headers["ExportFlow-Signature"],
93
+ secret=WEBHOOK_SECRET,
94
+ )
95
+ except SignatureVerificationError:
96
+ return 400
97
+ if already_handled(event.id): # deliveries are at-least-once
98
+ return 200
99
+ if event.type == "document.processing.completed":
100
+ document_id = event.data["document"]["id"]
101
+ ...
102
+ return 200
103
+ ```
104
+
105
+ Answer with any `2xx` within 10 seconds. Anything else is retried with
106
+ increasing delays for about two and a half hours. An event carries identifiers
107
+ and statuses, never document content: fetch the resource to read it.
108
+
109
+ | Event | When |
110
+ |---|---|
111
+ | `document.uploaded` | A document was accepted, scanned and stored |
112
+ | `document.processing.started` | Classification began |
113
+ | `document.processing.completed` | The document was classified and read |
114
+ | `document.processing.failed` | Processing stopped; `GET` the document for why |
115
+ | `verification.completed` | A verification finished; fetch it for the findings |
116
+ | `verification.failed` | A verification could not run |
117
+ | `billing.invoice.generated` | An invoice was issued to your organisation |
118
+
119
+ ## Errors
120
+
121
+ Every error is an `exportflow.ExportFlowError`. One returned by the API is an
122
+ `APIError` with a stable `code`, the HTTP `status_code` and a `request_id` to
123
+ quote to support. Branch on the type or the code, never on the message.
124
+
125
+ ```python
126
+ import exportflow
127
+
128
+ try:
129
+ client.verifications.create(shipment_id)
130
+ except exportflow.ConflictError as error:
131
+ if error.code == "not_confirmed":
132
+ ... # ask the user to confirm the record
133
+ except exportflow.QuotaExceededError:
134
+ ... # the month's allowance is used up
135
+ except exportflow.RateLimitError as error:
136
+ time.sleep(error.retry_after or 5)
137
+ except exportflow.APIConnectionError:
138
+ ... # safe to retry later
139
+ ```
140
+
141
+ | Exception | HTTP | Meaning |
142
+ |---|---|---|
143
+ | `AuthenticationError` | 401 | Key missing, wrong, revoked or expired |
144
+ | `PermissionDeniedError` | 403 | Missing scope, suspended account, network not allowed |
145
+ | `NotFoundError` | 404 | No such resource in this organisation and environment |
146
+ | `ConflictError` | 409 | Not possible in the current state (e.g. `not_confirmed`) |
147
+ | `InvalidRequestError` | 400, 422 | The request is wrong; do not retry |
148
+ | `RateLimitError` | 429 | Slow down; see `retry_after` |
149
+ | `QuotaExceededError` | 429 | Allowance or spending limit reached for the period |
150
+ | `SDKVersionUnsupportedError` | 426 | Upgrade this SDK |
151
+ | `ServerError` | 5xx | ExportFlow's fault; retried automatically |
152
+ | `APIConnectionError`, `APITimeoutError` | none | The API could not be reached |
153
+
154
+ ## Retries and idempotency
155
+
156
+ The client retries connection failures, timeouts, `429 rate_limited` and `5xx`
157
+ up to `max_retries` times (default 2), with backoff, honouring `Retry-After`.
158
+
159
+ Every request that changes something carries an `Idempotency-Key`, and a retry
160
+ sends the *same* key and the *same* body, so the work is done once however many
161
+ times it is sent. To make your own retries safe across process restarts, pass
162
+ your own key:
163
+
164
+ ```python
165
+ client.documents.upload(file="bl.pdf", shipment_id=sid, idempotency_key=f"upload-{erp_document_id}")
166
+ ```
167
+
168
+ Uploading the same file to the same shipment twice returns the first document
169
+ (`duplicate=True`) and is not charged again.
170
+
171
+ ## Usage and charges
172
+
173
+ ```python
174
+ usage = client.usage.get() # this month; or client.usage.get("2026-10")
175
+ for meter in usage.meters:
176
+ print(meter.name, meter.succeeded, "of", meter.quota or "unlimited")
177
+ print(usage.estimated_charge, usage.currency)
178
+ ```
179
+
180
+ Requests are not charged. Usage is recorded when work completes: a document
181
+ accepted, a document processed, a verification completed. Sandbox usage is
182
+ measured and never charged. Charges are estimates until an invoice is issued.
183
+
184
+ ## Configuration
185
+
186
+ ```python
187
+ ExportFlow(
188
+ api_key=None, # default: EXPORTFLOW_API_KEY
189
+ base_url=None, # default: EXPORTFLOW_BASE_URL or https://api.exportflow.app
190
+ timeout=60.0, # seconds per response
191
+ max_retries=2,
192
+ http_client=None, # your own httpx.Client: proxies, TLS, tests
193
+ )
194
+ ```
195
+
196
+ The client is safe to share between threads. Use it as a context manager, or
197
+ call `close()`, to release its connections.
198
+
199
+ ## Versioning
200
+
201
+ This SDK follows semantic versioning and speaks version 1 of the API. Within
202
+ version 1 the API only adds; nothing is removed or changed in meaning, so this
203
+ SDK keeps working as the API grows, and unknown fields are available on every
204
+ model's `.raw`. If the server ever reports a different major version the
205
+ client raises `IncompatibleAPIVersionError` rather than misread a response.
206
+ See `CHANGELOG.md`.
@@ -0,0 +1,30 @@
1
+ [project]
2
+ name = "exportflow"
3
+ version = "1.0.0"
4
+ description = "The official Python SDK for the ExportFlow document processing and verification API"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = { text = "Proprietary" }
8
+ authors = [{ name = "MeerTech Ltd" }]
9
+ keywords = ["exportflow", "documents", "ocr", "trade", "verification", "erp"]
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Typing :: Typed",
13
+ "Intended Audience :: Developers",
14
+ ]
15
+ dependencies = ["httpx>=0.25,<1"]
16
+
17
+ [project.urls]
18
+ Documentation = "https://exportflow.app/developers"
19
+ Changelog = "https://exportflow.app/developers/changelog"
20
+
21
+ [build-system]
22
+ requires = ["hatchling"]
23
+ build-backend = "hatchling.build"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["src/exportflow"]
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
30
+ pythonpath = ["src"]