Developers

An ERP you can build against

Everything the product does rides on the same open surface: a PAT-only REST API, discoverable per-tenant models, HMAC-signed webhooks and a test zone that deletes itself.

Get started in three steps

No SDK required — a key, a base URL and the interactive reference are all a first call needs.

1

Mint a key

Console → API keys. Pick Test to get an sk_test_ key bound to a sandbox twin of your organization — or Live for production.

2

Authenticate

Send the key as a Bearer header: Authorization: Bearer sk_… Scopes can bound a key to exactly the entities and verbs it needs.

3

Open the reference

The Swagger UI documents every route, filter and lookup — including discovery and webhooks — straight from the live schema.

Base URL: https://updo.pro/api/public/v1 · Interactive API reference

A Python SDK, and a command line

`updo-sdk` wraps the public API: same token, same contract, in Python objects. It reads `UPDO_API_TOKEN` from the environment — so an `sk_test_` key points it at your test zone and an `sk_` key at your real data, without touching a line of code.

pip install updo-sdk
pip install "updo-sdk[cli]"   # + the updo command

Synchronous

from updo import UpdoClient

# Or: export UPDO_API_TOKEN=sk_test_...  (UPDO_BASE_URL to point elsewhere)
with UpdoClient(token="sk_test_...") as client:
    print(client.me().tenant_slug)

    products = client.entity("product")
    for p in products.iterate(where={"status": "active"}):
        print(p["sku"])

Asynchronous

from updo import AsyncUpdoClient

async with AsyncUpdoClient(token="sk_test_...") as client:
    products = await client.entity("product")
    async for p in products.iterate(where={"status": "active"}):
        print(p["sku"])

What the client raises

AuthenticationError

Token missing, revoked or expired (401).

PermissionDenied

The token is valid but the access decision says no, or its scopes do not cover the gesture (403).

ValidationError

The server refused the value — required field, undeclared choice, business rule (400).

NotFoundError

The record does not exist, or not in this organization (404).

ApprovalRequired

NOT an error: the transition is parked awaiting approval (202). The request id is carried on the exception.

From the command line

Installed with the `[cli]` extra. Handy to explore an entity, pull an export, or generate typed models from YOUR organization's data model.

updo get product --where status=active --limit 20
updo export product --format xlsx --out products.xlsx
updo codegen --out models.py

Package, releases and full reference: pypi.org/project/updo-sdk

Discover the model

Entities are defined per tenant, so the API tells you what exists: GET /data/ lists every entity your key may read, each with its schema URL. A denied entity is simply absent — the index is never an oracle.

Read and write records

Generic record envelopes, data__<field> filters with lookups, free-text search, aggregate and pivot for BI, CSV/XLSX export — all bounded by the same access decisions as the product itself.

curl https://updo.pro/api/public/v1/data/ \
  -H "Authorization: Bearer sk_test_..."

Webhooks, managed by the API

Subscribe to any business event — invoice.paid, ticket.created, your own emitted events — straight from the API. Every delivery is journaled per attempt, with the HTTP code, duration and next retry readable through the same key.

curl -X POST https://updo.pro/api/public/v1/webhooks/ \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "invoice-paid",
    "event_pattern": "invoice.paid",
    "config": {
      "url": "https://example.com/hooks/updo",
      "secret": "whsec_..."
    }
  }'

The signature contract

X-Qlaris-Signature

sha256=<hex> — HMAC-SHA256 of the exact request body bytes, keyed with your subscription secret.

X-Qlaris-Event

The event name that fired the delivery.

X-Qlaris-Delivery

Stable across one event's retries — deduplicate on it.

X-Qlaris-Timestamp

Unix seconds at send time. Reject anything older than 5 minutes to block replays.

import hashlib, hmac

def verify(secret: str, raw_body: bytes, signature: str) -> bool:
    digest = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={digest}", signature)

A test zone that cleans up after itself

An sk_test_ key opens a sandbox twin of your organization: same modules, same model, seeded data — and a hard boundary with production.

A real twin

The sandbox is provisioned by the same installer as production — what works there works live.

Harmless by construction

Documents render with a NOT-TO-USE watermark and no email ever actually leaves the sandbox.

Least privilege

Scope a key to data:<entity>:read, analytics:*, webhooks:write… Empty scopes mean the full rights of the key's roles.

Evaluate the platform on your own operations

Create your workspace in minutes, with your profession's applications already in place. No credit card.