POST /api/v1/orders with a Bearer token, or sign a JSON body with your store’s secret and post it to a webhook URL. Repeats are safe: the same order ID never creates a second invoice.Two ways in
Mavri’s API is served by your own installation, so every URL below points at your server, not at a hosted service. There are two common routes for turning outside events into invoices:
- The REST API, for scripts and tools you control. You authenticate with an API key.
- Signed webhooks, for shops that push orders to you. The sender signs the request with a shared secret instead of using an API key.
1. Make an API key
Open Settings → API keys and create one. The secret looks like mvr_… and is shown once, so store it in a password manager or your secret store. Keys can do everything except manage keys and restore backups. Treat one like a password: keep it out of source control and out of browser code.
2. Create an invoice from an order
First connect a store on the Stores page. A store connection supplies the business, currency, bank account, style and default tax, and gives you its shopId. Then:
curl -X POST http://localhost:3001/api/v1/orders \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"shopId":1,"externalOrderId":"ORD-1001","customer":{"name":"Acme Ltd","email":"billing@acme.test"},"lineItems":[{"name":"Consulting","quantity":2,"unitPriceMajor":150}]}'The reply is {"documentId": 42, "duplicate": false} with status 201. Send the same externalOrderId again and you get the same invoice back with "duplicate": true and status 200. That idempotency means a retrying client cannot double-bill.
3. Signed webhooks
Shops post orders to /api/hooks/{platform}/{shopId}. The platform is shopify, woocommerce or generic. The raw request body must be signed with the connection’s secret using HMAC-SHA256:
| Platform | Header | Encoding |
|---|---|---|
| shopify | X-Shopify-Hmac-Sha256 | base64 |
| woocommerce | X-WC-Webhook-Signature | base64 |
| generic | X-Webhook-Signature | hex |
Shopify and WooCommerce already sign this way, so you paste the webhook URL and secret into their settings. For anything else, use generic. The body is the same JSON as the orders endpoint, without shopId. A small signer in Python:
import hmac, hashlib, json, urllib.request
secret = b"YOUR_STORE_SIGNING_SECRET"
body = json.dumps({"externalOrderId": "ORD-1002",
"customer": {"name": "Acme Ltd"},
"lineItems": [{"name": "Consulting", "quantity": 1, "unitPriceMajor": 150}]}).encode()
sig = hmac.new(secret, body, hashlib.sha256).hexdigest()
req = urllib.request.Request("http://localhost:3001/api/hooks/generic/1", data=body,
headers={"Content-Type": "application/json", "X-Webhook-Signature": sig})
print(urllib.request.urlopen(req).read())Sign the exact bytes you send; re-serialising the JSON after signing changes the signature. A wrong signature returns 401, a paused connection 403, and each connection is limited to 120 requests a minute.
Good habits
- Use HTTPS for anything reachable beyond your own network.
- Money is in minor units. Fields ending in
Centsare whole cents; the order endpoint takes major units (unitPriceMajor). - Read errors. Failures come back as
{"error": {"code", "message"}}with a matching status such as400,404,409or422. - One key per tool, so you can delete a single one if it leaks.
The MCP server
For AI clients there is a separate program, mavri-mcp, a stdio MCP server in the same image. It reads an API key from MAVRI_API_KEY and exposes tools such as listing and creating documents, recording a payment and exporting an e-invoice. Give an assistant a key only if you are comfortable with it acting on your invoices.