myocr-client 0.2.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.
- myocr_client-0.2.0/.gitignore +74 -0
- myocr_client-0.2.0/CHANGELOG.md +34 -0
- myocr_client-0.2.0/LICENSE +21 -0
- myocr_client-0.2.0/PKG-INFO +274 -0
- myocr_client-0.2.0/README.md +237 -0
- myocr_client-0.2.0/myocr_client/__init__.py +63 -0
- myocr_client-0.2.0/myocr_client/client.py +374 -0
- myocr_client-0.2.0/myocr_client/exceptions.py +195 -0
- myocr_client-0.2.0/myocr_client/models.py +201 -0
- myocr_client-0.2.0/myocr_client/webhook.py +68 -0
- myocr_client-0.2.0/pyproject.toml +66 -0
- myocr_client-0.2.0/tests/test_client.py +490 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
.env
|
|
3
|
+
.env.example
|
|
4
|
+
.env.production
|
|
5
|
+
.env.staging
|
|
6
|
+
.env.local
|
|
7
|
+
|
|
8
|
+
# macOS
|
|
9
|
+
.DS_Store
|
|
10
|
+
.AppleDouble
|
|
11
|
+
.LSOverride
|
|
12
|
+
|
|
13
|
+
# Office temp files
|
|
14
|
+
~$*
|
|
15
|
+
|
|
16
|
+
# Documents & spreadsheets
|
|
17
|
+
*.xlsx
|
|
18
|
+
*.docx
|
|
19
|
+
*.pdf
|
|
20
|
+
|
|
21
|
+
# Google Ads campaigns
|
|
22
|
+
google_ads_campaign/
|
|
23
|
+
|
|
24
|
+
# Python
|
|
25
|
+
__pycache__/
|
|
26
|
+
*.py[cod]
|
|
27
|
+
*.pyo
|
|
28
|
+
*.pyd
|
|
29
|
+
.Python
|
|
30
|
+
venv/
|
|
31
|
+
env/
|
|
32
|
+
.venv/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
dist/
|
|
35
|
+
build/
|
|
36
|
+
|
|
37
|
+
# Logs
|
|
38
|
+
*.log
|
|
39
|
+
|
|
40
|
+
# GeoIP database (8.9MB binary, third-party, upload manually to server)
|
|
41
|
+
config/GeoLite2-Country.mmdb
|
|
42
|
+
|
|
43
|
+
.claude/
|
|
44
|
+
|
|
45
|
+
# Corpus immagini di test (~1.3GB) e script di benchmark scratch — non versionare
|
|
46
|
+
_TEST_DIVERSI/
|
|
47
|
+
bench_*.py
|
|
48
|
+
|
|
49
|
+
# Screenshot del test run (artefatti rigenerabili via Playwright) — non versionare
|
|
50
|
+
notes/img/test/
|
|
51
|
+
|
|
52
|
+
# Cache runtime + file temporanei (creati sul server)
|
|
53
|
+
gemini_chunk_cache/
|
|
54
|
+
upload_files_home/
|
|
55
|
+
processed_files_home/
|
|
56
|
+
|
|
57
|
+
# Indice KB del chatbot (artefatto derivato, ~MB) — rigenerabile con: python -m chatbot.kb_build
|
|
58
|
+
chatbot/kb_index.pkl
|
|
59
|
+
chatbot/kb_index.pkl.tmp
|
|
60
|
+
|
|
61
|
+
# artefatti grezzi test (contengono nomi file reali di clienti) — non versionare
|
|
62
|
+
notes/batch_corpus_results.csv
|
|
63
|
+
notes/BATCH_CORPUS_REPORT.md
|
|
64
|
+
notes/batch_corpus_tracebacks.txt
|
|
65
|
+
|
|
66
|
+
tests/qa/node_modules/
|
|
67
|
+
|
|
68
|
+
# Screenshot di QA add-in (artefatti, non versionati)
|
|
69
|
+
tests/qa/__screens__/
|
|
70
|
+
.publish_tokens
|
|
71
|
+
|
|
72
|
+
# dump DB produzione (dati personali, mai su git)
|
|
73
|
+
dump.sql
|
|
74
|
+
*.dump.sql
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `myocr-client` will be documented in this file.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Changed (BREAKING)
|
|
10
|
+
- Rinominata l'eccezione `AzureError` -> `OcrEngineError` e il codice errore `AZURE_ERROR` -> `OCR_ERROR`
|
|
11
|
+
(allineamento al motore OCR proprietario myocr; aggiornare gli import e gli `except`).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `client.usage()` — wraps `GET /v1/usage` to fetch current month quota
|
|
15
|
+
programmatically (plan/calls_used/calls_limit/percentage/reset_date).
|
|
16
|
+
|
|
17
|
+
## [0.1.0] — 2026-05-25 (initial)
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- `MyOCRClient` class with all v1 endpoints:
|
|
21
|
+
- `convert()` — sync conversion (xlsx / txt / json output)
|
|
22
|
+
- `create_job()` / `get_job()` / `get_job_result()` / `delete_job()` — async lifecycle
|
|
23
|
+
- `batch()` — 1–20 files per call
|
|
24
|
+
- `status()` — public health check
|
|
25
|
+
- `list_keys()` — administrative
|
|
26
|
+
- `Job` model with `wait()` polling (exponential backoff), `download()`, `delete()`.
|
|
27
|
+
- `JobStatus` enum (`pending`, `processing`, `done`, `failed`).
|
|
28
|
+
- `ConversionResult` / `JobResult` / `BatchResult` typed wrappers.
|
|
29
|
+
- 16 typed exceptions mapped 1:1 to API error codes (`QuotaExceeded`, `InvalidApiKey`, `OcrEngineError`, ...).
|
|
30
|
+
- `verify_webhook_signature()` helper for HMAC-SHA256 validation of incoming webhooks.
|
|
31
|
+
- Automatic retry on 429 / 5xx with exponential backoff (honors `Retry-After`).
|
|
32
|
+
- Accepts file path, bytes, or file-like input.
|
|
33
|
+
- Env var fallback `MYOCR_API_KEY`, `MYOCR_BASE_URL`.
|
|
34
|
+
- Full pytest suite with `responses` library mocking.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MAD.AI SRL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: myocr-client
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python SDK for myocr.app — convert PDFs and images to structured Excel using myocr's OCR engine.
|
|
5
|
+
Project-URL: Homepage, https://www.myocr.app
|
|
6
|
+
Project-URL: Documentation, https://www.myocr.app/docs/api
|
|
7
|
+
Project-URL: Repository, https://github.com/Selaf688/myocr-3.5
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/Selaf688/myocr-3.5/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Selaf688/myocr-3.5/blob/main/sdk/python/CHANGELOG.md
|
|
10
|
+
Author-email: "MAD.AI SRL" <info@myocr.app>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: api-client,document-extraction,excel,myocr,ocr,pdf
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Office/Business :: Office Suites
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Requires-Python: >=3.8
|
|
30
|
+
Requires-Dist: requests>=2.28
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: responses>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: twine>=4.0; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# myocr-client — Python SDK for myocr.app
|
|
39
|
+
|
|
40
|
+
Official Python client for the **[myocr.app](https://www.myocr.app)** API. Convert PDFs and images to structured Excel using myocr's OCR engine (invoice, receipt, bank statement, business card, generic tables, plain text).
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/myocr-client/)
|
|
43
|
+
[](https://pypi.org/project/myocr-client/)
|
|
44
|
+
[](https://opensource.org/licenses/MIT)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install myocr-client
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
Get an API key at [/account/api](https://www.myocr.app/account/api) (signup required), then:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from myocr_client import MyOCRClient
|
|
60
|
+
|
|
61
|
+
client = MyOCRClient(api_key="sk_live_...")
|
|
62
|
+
# or set MYOCR_API_KEY in env
|
|
63
|
+
|
|
64
|
+
# Synchronous conversion (≤5MB, ≤10 pages, returns immediately)
|
|
65
|
+
result = client.convert("invoice.pdf", model="invoice")
|
|
66
|
+
result.save("invoice.xlsx")
|
|
67
|
+
|
|
68
|
+
print(result.pages_used, result.model, result.request_id)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Models
|
|
72
|
+
|
|
73
|
+
| Model | Output | Best for |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `tables` | xlsx with generic tables | Any structured table |
|
|
76
|
+
| `text` | plain txt | OCR text extraction |
|
|
77
|
+
| `invoice` | xlsx with Vendor / Customer / Total / Line items | Invoices, bills |
|
|
78
|
+
| `receipt` | xlsx with Merchant / Date / Items / Total | Receipts |
|
|
79
|
+
| `bank_statement` | xlsx with Account / Transactions sheet | Bank statements |
|
|
80
|
+
| `business_card` | xlsx with Contact / Company / Phones / Emails | Business cards |
|
|
81
|
+
|
|
82
|
+
## Async jobs (files > 5MB or > 10 pages)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
job = client.create_job(
|
|
86
|
+
"annual_report.pdf",
|
|
87
|
+
model="bank_statement",
|
|
88
|
+
webhook_url="https://your.app/webhooks/myocr", # optional
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Option 1: polling with exponential backoff
|
|
92
|
+
job.wait(timeout=600)
|
|
93
|
+
job.download("report.xlsx")
|
|
94
|
+
|
|
95
|
+
# Option 2: notified via webhook (preferred for prod) — see "Webhook verification" below
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Batch (1–20 files in one call)
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
result = client.batch(
|
|
102
|
+
["a.pdf", "b.pdf", "c.pdf"],
|
|
103
|
+
model="invoice",
|
|
104
|
+
webhook_url="https://your.app/webhooks/myocr",
|
|
105
|
+
)
|
|
106
|
+
print(result.jobs_created, "jobs queued;", len(result.errors), "errors")
|
|
107
|
+
|
|
108
|
+
# Wait for all and download
|
|
109
|
+
for job in result.wait_all(timeout=1200):
|
|
110
|
+
if job.is_done:
|
|
111
|
+
job.download(f"{job.request_id}.xlsx")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Webhook verification
|
|
115
|
+
|
|
116
|
+
myocr signs every webhook with HMAC-SHA256 (header `X-MyOCR-Signature: sha256=<hex>`). Always verify before trusting the payload — and **use raw bytes**, not the parsed JSON:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from flask import Flask, request
|
|
120
|
+
from myocr_client import verify_webhook_signature
|
|
121
|
+
|
|
122
|
+
app = Flask(__name__)
|
|
123
|
+
SECRET = "your-shared-secret" # same as server WEBHOOK_SIGNING_SECRET
|
|
124
|
+
|
|
125
|
+
@app.route("/webhooks/myocr", methods=["POST"])
|
|
126
|
+
def myocr_webhook():
|
|
127
|
+
body = request.get_data() # raw bytes, NOT request.get_json()
|
|
128
|
+
sig = request.headers.get("X-MyOCR-Signature", "")
|
|
129
|
+
if not verify_webhook_signature(body, sig, SECRET):
|
|
130
|
+
return "invalid signature", 401
|
|
131
|
+
|
|
132
|
+
event = request.get_json() # safe now
|
|
133
|
+
# {"event": "job.completed", "data": {"request_id": "...", "status": "done", ...}}
|
|
134
|
+
return "", 200
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Events: `job.completed`, `job.failed`.
|
|
138
|
+
Retry policy: 1m → 5m → 30m → 2h (4 retries beyond the first attempt).
|
|
139
|
+
|
|
140
|
+
## Error handling
|
|
141
|
+
|
|
142
|
+
Every error code maps to a typed exception:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from myocr_client import MyOCRClient, QuotaExceeded, InvalidApiKey, OcrEngineError
|
|
146
|
+
|
|
147
|
+
client = MyOCRClient(api_key="sk_live_...")
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
result = client.convert("doc.pdf", model="invoice")
|
|
151
|
+
except QuotaExceeded as e:
|
|
152
|
+
print(f"Plan {e.current_plan}, used {e.calls_used}/{e.calls_limit}")
|
|
153
|
+
print(f"Upgrade: {e.upgrade_url}")
|
|
154
|
+
print(f"Resets: {e.reset_date}")
|
|
155
|
+
except InvalidApiKey:
|
|
156
|
+
print("Rotate your key from /account/api")
|
|
157
|
+
except OcrEngineError:
|
|
158
|
+
print("OCR engine upstream failure; safe to retry")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Exception | HTTP | Code |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `MissingApiKey` | 401 | `MISSING_API_KEY` |
|
|
164
|
+
| `InvalidApiKey` | 401 | `INVALID_API_KEY` |
|
|
165
|
+
| `UnsupportedModel` | 400 | `UNSUPPORTED_MODEL` |
|
|
166
|
+
| `UnsupportedFileType` | 400 | `UNSUPPORTED_FILE_TYPE` |
|
|
167
|
+
| `MissingFile` | 400 | `MISSING_FILE` |
|
|
168
|
+
| `FileTooLarge` | 413 | `FILE_TOO_LARGE` |
|
|
169
|
+
| `TooManyPages` | 413 | `TOO_MANY_PAGES` |
|
|
170
|
+
| `InvalidWebhookUrl` | 400 | `INVALID_WEBHOOK_URL` |
|
|
171
|
+
| `QuotaExceeded` | 402 | `QUOTA_EXCEEDED` |
|
|
172
|
+
| `NotReady` | 409 | `NOT_READY` |
|
|
173
|
+
| `NotFound` | 404 | `NOT_FOUND` |
|
|
174
|
+
| `OcrEngineError` | 502 | `OCR_ERROR` |
|
|
175
|
+
| `StorageError` | 503 | `STORAGE_ERROR` |
|
|
176
|
+
| `RateLimited` | 429 | — |
|
|
177
|
+
| `ServiceNotReady` | 503 | `SERVICE_NOT_READY` |
|
|
178
|
+
| `InternalError` | 500 | `INTERNAL_ERROR` |
|
|
179
|
+
|
|
180
|
+
The SDK automatically retries `429` and `5xx` responses up to 3 times with exponential backoff (honoring `Retry-After` when present). After retries exhausted the exception is raised.
|
|
181
|
+
|
|
182
|
+
## Input flexibility
|
|
183
|
+
|
|
184
|
+
`client.convert()` and `client.create_job()` accept:
|
|
185
|
+
|
|
186
|
+
- A file path: `client.convert("/path/to/doc.pdf", ...)`
|
|
187
|
+
- Raw bytes: `client.convert(pdf_bytes, filename="doc.pdf", ...)`
|
|
188
|
+
- A file-like object: `with open("doc.pdf", "rb") as f: client.convert(f, ...)`
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
| Argument | Env var | Default |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `api_key` | `MYOCR_API_KEY` | — (required) |
|
|
195
|
+
| `base_url` | `MYOCR_BASE_URL` | `https://api.myocr.app` |
|
|
196
|
+
| `timeout` | — | 60s |
|
|
197
|
+
| `retry_attempts` | — | 3 |
|
|
198
|
+
| `session` | — | new `requests.Session()` |
|
|
199
|
+
|
|
200
|
+
For staging:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
client = MyOCRClient(api_key="sk_test_...", base_url="https://beta.myocr.app")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Monitor your quota
|
|
207
|
+
|
|
208
|
+
Check current month usage programmatically (e.g. to upgrade before exhaustion):
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
usage = client.usage()
|
|
212
|
+
# {
|
|
213
|
+
# "plan": "free", "calls_used": 42, "calls_limit": 100,
|
|
214
|
+
# "percentage": 42.0, "reset_date": "2026-06-01T00:00:00",
|
|
215
|
+
# "year_month": "2026-05", "is_test_key": False
|
|
216
|
+
# }
|
|
217
|
+
if usage["percentage"] and usage["percentage"] > 80:
|
|
218
|
+
# alert ops, upgrade plan, or stop background workers
|
|
219
|
+
...
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Status & limits
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
status = client.status()
|
|
226
|
+
# {
|
|
227
|
+
# "service": "myocr.app API", "version": "v1",
|
|
228
|
+
# "models_supported": ["bank_statement", "business_card", ...],
|
|
229
|
+
# "features": {"sync_convert": True, "async_jobs": True, "webhook": True, ...},
|
|
230
|
+
# "limits": {"sync_max_bytes": 5242880, "sync_max_pages": 10,
|
|
231
|
+
# "jobs_max_bytes": 52428800, "sync_rate_per_minute": 60,
|
|
232
|
+
# "jobs_rate_per_minute": 120}
|
|
233
|
+
# }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Rate limits (server-side)
|
|
237
|
+
|
|
238
|
+
| Endpoint | Limit |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `POST /v1/convert` | 60 / min |
|
|
241
|
+
| `POST /v1/jobs` | 120 / min |
|
|
242
|
+
| `POST /v1/batch` | 30 / min |
|
|
243
|
+
|
|
244
|
+
The SDK handles `429` with automatic retry. If you saturate the quota, upgrade your plan from the dashboard.
|
|
245
|
+
|
|
246
|
+
## Reference
|
|
247
|
+
|
|
248
|
+
- **Full OpenAPI spec:** [openapi.yaml](https://www.myocr.app/static/openapi.yaml)
|
|
249
|
+
- **Interactive docs:** [/docs/api](https://www.myocr.app/docs/api) (Scalar UI)
|
|
250
|
+
- **Dashboard:** [/account/api](https://www.myocr.app/account/api) — manage keys, view usage, upgrade
|
|
251
|
+
- **Webhook signing secret:** generated when you create a webhook integration; shared via dashboard.
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
git clone https://github.com/Selaf688/myocr-3.5
|
|
257
|
+
cd myocr-3.5/sdk/python
|
|
258
|
+
pip install -e ".[dev]"
|
|
259
|
+
pytest -v
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Versioning
|
|
263
|
+
|
|
264
|
+
Semantic versioning. The API itself is `v1` and stable; the SDK can release patch/minor independently.
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT. See [LICENSE](./LICENSE).
|
|
269
|
+
|
|
270
|
+
## Support
|
|
271
|
+
|
|
272
|
+
- Documentation: <https://www.myocr.app/docs/api>
|
|
273
|
+
- Email: info@myocr.app
|
|
274
|
+
- Issues: <https://github.com/Selaf688/myocr-3.5/issues>
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# myocr-client — Python SDK for myocr.app
|
|
2
|
+
|
|
3
|
+
Official Python client for the **[myocr.app](https://www.myocr.app)** API. Convert PDFs and images to structured Excel using myocr's OCR engine (invoice, receipt, bank statement, business card, generic tables, plain text).
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/myocr-client/)
|
|
6
|
+
[](https://pypi.org/project/myocr-client/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install myocr-client
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
Get an API key at [/account/api](https://www.myocr.app/account/api) (signup required), then:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from myocr_client import MyOCRClient
|
|
23
|
+
|
|
24
|
+
client = MyOCRClient(api_key="sk_live_...")
|
|
25
|
+
# or set MYOCR_API_KEY in env
|
|
26
|
+
|
|
27
|
+
# Synchronous conversion (≤5MB, ≤10 pages, returns immediately)
|
|
28
|
+
result = client.convert("invoice.pdf", model="invoice")
|
|
29
|
+
result.save("invoice.xlsx")
|
|
30
|
+
|
|
31
|
+
print(result.pages_used, result.model, result.request_id)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Models
|
|
35
|
+
|
|
36
|
+
| Model | Output | Best for |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `tables` | xlsx with generic tables | Any structured table |
|
|
39
|
+
| `text` | plain txt | OCR text extraction |
|
|
40
|
+
| `invoice` | xlsx with Vendor / Customer / Total / Line items | Invoices, bills |
|
|
41
|
+
| `receipt` | xlsx with Merchant / Date / Items / Total | Receipts |
|
|
42
|
+
| `bank_statement` | xlsx with Account / Transactions sheet | Bank statements |
|
|
43
|
+
| `business_card` | xlsx with Contact / Company / Phones / Emails | Business cards |
|
|
44
|
+
|
|
45
|
+
## Async jobs (files > 5MB or > 10 pages)
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
job = client.create_job(
|
|
49
|
+
"annual_report.pdf",
|
|
50
|
+
model="bank_statement",
|
|
51
|
+
webhook_url="https://your.app/webhooks/myocr", # optional
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Option 1: polling with exponential backoff
|
|
55
|
+
job.wait(timeout=600)
|
|
56
|
+
job.download("report.xlsx")
|
|
57
|
+
|
|
58
|
+
# Option 2: notified via webhook (preferred for prod) — see "Webhook verification" below
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Batch (1–20 files in one call)
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
result = client.batch(
|
|
65
|
+
["a.pdf", "b.pdf", "c.pdf"],
|
|
66
|
+
model="invoice",
|
|
67
|
+
webhook_url="https://your.app/webhooks/myocr",
|
|
68
|
+
)
|
|
69
|
+
print(result.jobs_created, "jobs queued;", len(result.errors), "errors")
|
|
70
|
+
|
|
71
|
+
# Wait for all and download
|
|
72
|
+
for job in result.wait_all(timeout=1200):
|
|
73
|
+
if job.is_done:
|
|
74
|
+
job.download(f"{job.request_id}.xlsx")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Webhook verification
|
|
78
|
+
|
|
79
|
+
myocr signs every webhook with HMAC-SHA256 (header `X-MyOCR-Signature: sha256=<hex>`). Always verify before trusting the payload — and **use raw bytes**, not the parsed JSON:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from flask import Flask, request
|
|
83
|
+
from myocr_client import verify_webhook_signature
|
|
84
|
+
|
|
85
|
+
app = Flask(__name__)
|
|
86
|
+
SECRET = "your-shared-secret" # same as server WEBHOOK_SIGNING_SECRET
|
|
87
|
+
|
|
88
|
+
@app.route("/webhooks/myocr", methods=["POST"])
|
|
89
|
+
def myocr_webhook():
|
|
90
|
+
body = request.get_data() # raw bytes, NOT request.get_json()
|
|
91
|
+
sig = request.headers.get("X-MyOCR-Signature", "")
|
|
92
|
+
if not verify_webhook_signature(body, sig, SECRET):
|
|
93
|
+
return "invalid signature", 401
|
|
94
|
+
|
|
95
|
+
event = request.get_json() # safe now
|
|
96
|
+
# {"event": "job.completed", "data": {"request_id": "...", "status": "done", ...}}
|
|
97
|
+
return "", 200
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Events: `job.completed`, `job.failed`.
|
|
101
|
+
Retry policy: 1m → 5m → 30m → 2h (4 retries beyond the first attempt).
|
|
102
|
+
|
|
103
|
+
## Error handling
|
|
104
|
+
|
|
105
|
+
Every error code maps to a typed exception:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from myocr_client import MyOCRClient, QuotaExceeded, InvalidApiKey, OcrEngineError
|
|
109
|
+
|
|
110
|
+
client = MyOCRClient(api_key="sk_live_...")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
result = client.convert("doc.pdf", model="invoice")
|
|
114
|
+
except QuotaExceeded as e:
|
|
115
|
+
print(f"Plan {e.current_plan}, used {e.calls_used}/{e.calls_limit}")
|
|
116
|
+
print(f"Upgrade: {e.upgrade_url}")
|
|
117
|
+
print(f"Resets: {e.reset_date}")
|
|
118
|
+
except InvalidApiKey:
|
|
119
|
+
print("Rotate your key from /account/api")
|
|
120
|
+
except OcrEngineError:
|
|
121
|
+
print("OCR engine upstream failure; safe to retry")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Exception | HTTP | Code |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `MissingApiKey` | 401 | `MISSING_API_KEY` |
|
|
127
|
+
| `InvalidApiKey` | 401 | `INVALID_API_KEY` |
|
|
128
|
+
| `UnsupportedModel` | 400 | `UNSUPPORTED_MODEL` |
|
|
129
|
+
| `UnsupportedFileType` | 400 | `UNSUPPORTED_FILE_TYPE` |
|
|
130
|
+
| `MissingFile` | 400 | `MISSING_FILE` |
|
|
131
|
+
| `FileTooLarge` | 413 | `FILE_TOO_LARGE` |
|
|
132
|
+
| `TooManyPages` | 413 | `TOO_MANY_PAGES` |
|
|
133
|
+
| `InvalidWebhookUrl` | 400 | `INVALID_WEBHOOK_URL` |
|
|
134
|
+
| `QuotaExceeded` | 402 | `QUOTA_EXCEEDED` |
|
|
135
|
+
| `NotReady` | 409 | `NOT_READY` |
|
|
136
|
+
| `NotFound` | 404 | `NOT_FOUND` |
|
|
137
|
+
| `OcrEngineError` | 502 | `OCR_ERROR` |
|
|
138
|
+
| `StorageError` | 503 | `STORAGE_ERROR` |
|
|
139
|
+
| `RateLimited` | 429 | — |
|
|
140
|
+
| `ServiceNotReady` | 503 | `SERVICE_NOT_READY` |
|
|
141
|
+
| `InternalError` | 500 | `INTERNAL_ERROR` |
|
|
142
|
+
|
|
143
|
+
The SDK automatically retries `429` and `5xx` responses up to 3 times with exponential backoff (honoring `Retry-After` when present). After retries exhausted the exception is raised.
|
|
144
|
+
|
|
145
|
+
## Input flexibility
|
|
146
|
+
|
|
147
|
+
`client.convert()` and `client.create_job()` accept:
|
|
148
|
+
|
|
149
|
+
- A file path: `client.convert("/path/to/doc.pdf", ...)`
|
|
150
|
+
- Raw bytes: `client.convert(pdf_bytes, filename="doc.pdf", ...)`
|
|
151
|
+
- A file-like object: `with open("doc.pdf", "rb") as f: client.convert(f, ...)`
|
|
152
|
+
|
|
153
|
+
## Configuration
|
|
154
|
+
|
|
155
|
+
| Argument | Env var | Default |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `api_key` | `MYOCR_API_KEY` | — (required) |
|
|
158
|
+
| `base_url` | `MYOCR_BASE_URL` | `https://api.myocr.app` |
|
|
159
|
+
| `timeout` | — | 60s |
|
|
160
|
+
| `retry_attempts` | — | 3 |
|
|
161
|
+
| `session` | — | new `requests.Session()` |
|
|
162
|
+
|
|
163
|
+
For staging:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
client = MyOCRClient(api_key="sk_test_...", base_url="https://beta.myocr.app")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Monitor your quota
|
|
170
|
+
|
|
171
|
+
Check current month usage programmatically (e.g. to upgrade before exhaustion):
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
usage = client.usage()
|
|
175
|
+
# {
|
|
176
|
+
# "plan": "free", "calls_used": 42, "calls_limit": 100,
|
|
177
|
+
# "percentage": 42.0, "reset_date": "2026-06-01T00:00:00",
|
|
178
|
+
# "year_month": "2026-05", "is_test_key": False
|
|
179
|
+
# }
|
|
180
|
+
if usage["percentage"] and usage["percentage"] > 80:
|
|
181
|
+
# alert ops, upgrade plan, or stop background workers
|
|
182
|
+
...
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Status & limits
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
status = client.status()
|
|
189
|
+
# {
|
|
190
|
+
# "service": "myocr.app API", "version": "v1",
|
|
191
|
+
# "models_supported": ["bank_statement", "business_card", ...],
|
|
192
|
+
# "features": {"sync_convert": True, "async_jobs": True, "webhook": True, ...},
|
|
193
|
+
# "limits": {"sync_max_bytes": 5242880, "sync_max_pages": 10,
|
|
194
|
+
# "jobs_max_bytes": 52428800, "sync_rate_per_minute": 60,
|
|
195
|
+
# "jobs_rate_per_minute": 120}
|
|
196
|
+
# }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Rate limits (server-side)
|
|
200
|
+
|
|
201
|
+
| Endpoint | Limit |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `POST /v1/convert` | 60 / min |
|
|
204
|
+
| `POST /v1/jobs` | 120 / min |
|
|
205
|
+
| `POST /v1/batch` | 30 / min |
|
|
206
|
+
|
|
207
|
+
The SDK handles `429` with automatic retry. If you saturate the quota, upgrade your plan from the dashboard.
|
|
208
|
+
|
|
209
|
+
## Reference
|
|
210
|
+
|
|
211
|
+
- **Full OpenAPI spec:** [openapi.yaml](https://www.myocr.app/static/openapi.yaml)
|
|
212
|
+
- **Interactive docs:** [/docs/api](https://www.myocr.app/docs/api) (Scalar UI)
|
|
213
|
+
- **Dashboard:** [/account/api](https://www.myocr.app/account/api) — manage keys, view usage, upgrade
|
|
214
|
+
- **Webhook signing secret:** generated when you create a webhook integration; shared via dashboard.
|
|
215
|
+
|
|
216
|
+
## Development
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
git clone https://github.com/Selaf688/myocr-3.5
|
|
220
|
+
cd myocr-3.5/sdk/python
|
|
221
|
+
pip install -e ".[dev]"
|
|
222
|
+
pytest -v
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Versioning
|
|
226
|
+
|
|
227
|
+
Semantic versioning. The API itself is `v1` and stable; the SDK can release patch/minor independently.
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT. See [LICENSE](./LICENSE).
|
|
232
|
+
|
|
233
|
+
## Support
|
|
234
|
+
|
|
235
|
+
- Documentation: <https://www.myocr.app/docs/api>
|
|
236
|
+
- Email: info@myocr.app
|
|
237
|
+
- Issues: <https://github.com/Selaf688/myocr-3.5/issues>
|