Quickstart
From nothing to a read invoice. You need an Ocost account as its owner or accountant; the examples use $OCOST_KEY for your key.
1. Sign up
Sign up at https://app.ocost.io/auth/sign-up, or sign in. Files you send show in Files like any upload, and Ocost files, books and matches them.
2. Make a key
- Open Settings › API.
- Choose Make key, name it after its user ("Odoo production"), keep all three scopes, choose Make key.
- Choose Copy key. It starts with
ocost_sk_and is shown once.
export OCOST_KEY="ocost_sk_…"3. Send a PDF
POST /public/v1/files takes the file as multipart/form-data in file; external_ref is your own id, echoed back.
curl -sS https://api.ocost.io/public/v1/files \
-H "Authorization: Bearer $OCOST_KEY" \
-F "file=@invoice.pdf" \
-F "external_ref=account.move,42"import os
import requests
API = "https://api.ocost.io/public/v1"
auth = {"Authorization": f"Bearer {os.environ['OCOST_KEY']}"}
with open("invoice.pdf", "rb") as f:
sent = requests.post(
f"{API}/files",
headers=auth,
files={"file": ("invoice.pdf", f, "application/pdf")},
data={"external_ref": "account.move,42"},
timeout=30,
)
sent.raise_for_status()
file = sent.json()import { readFile } from 'node:fs/promises';
const API = 'https://api.ocost.io/public/v1';
const auth = { Authorization: `Bearer ${process.env.OCOST_KEY}` };
const form = new FormData();
form.append(
'file',
new Blob([await readFile('invoice.pdf')], { type: 'application/pdf' }),
'invoice.pdf',
);
form.append('external_ref', 'account.move,42');
const sent = await fetch(`${API}/files`, { method: 'POST', headers: auth, body: form });
let file = await sent.json();The answer is 202 Accepted:
{
"id": "01a1b08c-6e3a-7562-8f4a-7924722667b1",
"name": "invoice.pdf",
"status": "received",
"reason": null,
"external_ref": "account.move,42",
"documents": [],
"sha256": "0edd5593…"
}The same bytes again answer 200 OK with the same file, counted once. X-Ocost-Documents-Left is what your plan has left this month.
4. Poll until it is read
GET /public/v1/files/{file_id} answers the file as it is now. Poll every few seconds while status is received or reading (most PDFs take under a minute), then less often.
curl -sS https://api.ocost.io/public/v1/files/$FILE_ID \
-H "Authorization: Bearer $OCOST_KEY"import time
for wait in [2, 2, 3, 5, 5, 10, 10, 15, 30, 30]:
file = requests.get(f"{API}/files/{file['id']}", headers=auth, timeout=15).json()
if file["status"] not in ("received", "reading"):
break
time.sleep(wait)while (file.status === 'received' || file.status === 'reading') {
await new Promise((done) => setTimeout(done, 3000));
file = await (await fetch(`${API}/files/${file.id}`, { headers: auth })).json();
}Then documents lists what it holds; not_read and failed carry a reason (Files and statuses).
5. Read the document
GET /public/v1/files/{file_id}/document answers its first document as ocost.document.v1, each value with where it was read and how sure Ocost is (The document schema). GET /public/v1/documents/{document_id} answers any other.
curl -sS https://api.ocost.io/public/v1/files/$FILE_ID/document \
-H "Authorization: Bearer $OCOST_KEY"document = requests.get(
f"{API}/files/{file['id']}/document", headers=auth, timeout=15
).json()
print(document["number"], document["totals"]["gross"], document["currency"])const document = await (
await fetch(`${API}/files/${file.id}/document`, { headers: auth })
).json();
console.log(document.number, document.totals.gross, document.currency);6. Search
GET /public/v1/search finds documents by words and by meaning.
curl -sS -G https://api.ocost.io/public/v1/search \
-H "Authorization: Bearer $OCOST_KEY" \
--data-urlencode "q=office chairs" \
--data-urlencode "kind=invoice"found = requests.get(
f"{API}/search", headers=auth, params={"q": "office chairs", "kind": "invoice"}, timeout=15
).json()
for r in found["results"]:
print(r["number"], r["seller"], r["issue_date"])const params = new URLSearchParams({ q: 'office chairs', kind: 'invoice' });
const found = await (await fetch(`${API}/search?${params}`, { headers: auth })).json();
for (const r of found.results) console.log(r.number, r.seller, r.issue_date);Next
- Statuses and reasons: Files and statuses
- Every field: The document schema
- Vendor bills in Odoo: Connecting Odoo
- Every route: API reference