Send Invoice — curl Examples
Complete examples for looking up recipients, sending invoices, handling idempotency, and dealing with errors.
All examples use the sandbox base URL (api.sandbox.invostaq.com) with a test key. Replace with api.invostaq.com and a sk_live_ key for production.
1. Look up a recipient
Check if a Peppol participant is registered and get their routing reference.
curl "https://api.sandbox.invostaq.com/api/participants/lookup?participantId=0196:971501234567" \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm"
{
"participantId": "0196:971501234567",
"isRegistered": true,
"accessPointRef": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"supportedDocTypes": [
"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1"
],
"provider": "InvoStaq"
}
If isRegistered is false, the recipient is not reachable on the Peppol network.
2. Send an invoice
Use the accessPointRef from the lookup response in the routing object.
curl -X POST https://api.sandbox.invostaq.com/api/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-001-attempt-1" \
-d '{
"invoice": {
"number": "INV-2024-001",
"issueDate": "2024-06-15",
"dueDate": "2024-07-15",
"currency": "EUR",
"supplier": {
"name": "Acme Trading NV",
"taxId": "BE0417497106",
"address": {
"line1": "Rue de la Loi 1",
"city": "Brussels",
"postalCode": "1000",
"countryCode": "BE"
}
},
"buyer": {
"name": "Gulf Imports Co",
"taxId": "971501234567",
"address": {
"line1": "Sheikh Zayed Road",
"city": "Dubai",
"countryCode": "AE"
}
},
"lineItems": [
{
"description": "Consulting services — June 2024",
"quantity": 1,
"unitPrice": 1000.00,
"taxRate": 21.0
}
],
"totals": {
"subtotal": 1000.00,
"taxAmount": 210.00,
"grandTotal": 1210.00
}
},
"routing": {
"senderParticipantId": "0208:0417497106",
"receiverParticipantId": "0196:971501234567",
"accessPointRef": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}'
{
"invoiceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"transactionId": "txn_abc123def456",
"status": "success",
"networkStatus": "Processing",
"idempotencyKeyEcho": "inv-2024-001-attempt-1",
"provider": "InvoStaq"
}
The invoice is now Processing. You receive a webhook when it becomes Delivered or Failed.
Tax rates are percentages. Use 21.0 for 21% VAT — not 0.21.
3. Multiple line items
curl -X POST https://api.sandbox.invostaq.com/api/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-002-attempt-1" \
-d '{
"invoice": {
"number": "INV-2024-002",
"issueDate": "2024-06-20",
"currency": "EUR",
"supplier": {
"name": "Acme Trading NV",
"taxId": "BE0417497106",
"address": { "line1": "Rue de la Loi 1", "city": "Brussels", "postalCode": "1000", "countryCode": "BE" }
},
"buyer": {
"name": "Gulf Imports Co",
"address": { "city": "Dubai", "countryCode": "AE" }
},
"lineItems": [
{
"description": "Ergonomic office chair",
"quantity": 5,
"unitPrice": 500.00,
"taxRate": 21.0,
"productCode": "FURN-CHAIR-ERG"
},
{
"description": "Standing desk converter",
"quantity": 2,
"unitPrice": 350.00,
"taxRate": 21.0,
"productCode": "FURN-DESK-STD"
}
],
"totals": {
"subtotal": 3200.00,
"taxAmount": 672.00,
"grandTotal": 3872.00
}
},
"routing": {
"senderParticipantId": "0208:0417497106",
"receiverParticipantId": "0196:971501234567",
"accessPointRef": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}'
Totals: Line 1 5 × 500 = 2500, Line 2 2 × 350 = 700, subtotal 3200, tax 3200 × 0.21 = 672, grand total 3872.
4. Idempotent retry
If your request times out, retry with the same Idempotency-Key. The API returns the original result without resubmitting to Peppol.
# First attempt (may have timed out)
curl -X POST https://api.sandbox.invostaq.com/api/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-001-attempt-1" \
-d '{ ... same invoice body ... }'
# Safe retry — returns original result, invoice only sent once
curl -X POST https://api.sandbox.invostaq.com/api/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-001-attempt-1" \
-d '{ ... same invoice body ... }'
The second response includes "Idempotency-Replay: true" in the headers and returns the same invoiceId. Both requests return 200 — the invoice is only sent once.
5. Check delivery artefacts
After sending, retrieve the Peppol delivery receipt from the recipient's Access Point.
curl "https://api.sandbox.invostaq.com/api/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/artefacts" \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm"
{
"invoiceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"items": [
{
"artefactId": "receipt-001",
"type": "RECEIPT",
"name": "AS4 receipt",
"contentType": "application/xml",
"createdAt": "2024-06-15T10:32:00Z"
}
],
"provider": "InvoStaq"
}
6. Error examples
Missing API key
curl -X POST https://api.sandbox.invostaq.com/api/invoices/send \
-H "Content-Type: application/json" \
-d '{}'
{
"type": "https://invostaq.com/errors/api-key-required",
"title": "API key required",
"detail": "Provide a valid InvoStaq API key in the x-api-key header.",
"status": 401
}
Totals mismatch
{
"type": "https://invostaq.com/errors/declared-totals-mismatch",
"title": "Declared totals do not reconcile",
"detail": "grandTotal (1210.00) must equal subtotal (1000.00) + taxAmount (150.00) = 1150.00.",
"status": 400
}
Rate limited
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"type": "https://invostaq.com/errors/rate-limit-exceeded",
"title": "Rate limit exceeded",
"detail": "Too many requests. Retry after the period specified in the Retry-After header.",
"status": 429
}
7. Complete script: lookup + send
#!/bin/bash
set -euo pipefail
API_KEY="sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm"
BASE="https://api.sandbox.invostaq.com/api"
SENDER="0208:0417497106"
RECEIVER="0196:971501234567"
# Step 1: Lookup
echo "Looking up $RECEIVER..."
LOOKUP=$(curl -sf "$BASE/participants/lookup?participantId=$RECEIVER" \
-H "x-api-key: $API_KEY")
IS_REGISTERED=$(echo "$LOOKUP" | jq -r '.isRegistered')
AP_REF=$(echo "$LOOKUP" | jq -r '.accessPointRef')
if [ "$IS_REGISTERED" != "true" ]; then
echo "Recipient not registered on Peppol"
exit 1
fi
echo "Registered. Access Point: $AP_REF"
# Step 2: Send
echo "Sending invoice..."
RESULT=$(curl -sf -X POST "$BASE/invoices/send" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{
\"invoice\": {
\"number\": \"INV-2024-001\",
\"issueDate\": \"2024-06-15\",
\"currency\": \"EUR\",
\"supplier\": {
\"name\": \"Acme Trading NV\",
\"taxId\": \"BE0417497106\",
\"address\": { \"line1\": \"Rue de la Loi 1\", \"city\": \"Brussels\", \"postalCode\": \"1000\", \"countryCode\": \"BE\" }
},
\"buyer\": {
\"name\": \"Gulf Imports Co\",
\"address\": { \"city\": \"Dubai\", \"countryCode\": \"AE\" }
},
\"lineItems\": [{
\"description\": \"Consulting services\",
\"quantity\": 1,
\"unitPrice\": 1000.00,
\"taxRate\": 21.0
}],
\"totals\": { \"subtotal\": 1000.00, \"taxAmount\": 210.00, \"grandTotal\": 1210.00 }
},
\"routing\": {
\"senderParticipantId\": \"$SENDER\",
\"receiverParticipantId\": \"$RECEIVER\",
\"accessPointRef\": \"$AP_REF\"
}
}")
INVOICE_ID=$(echo "$RESULT" | jq -r '.invoiceId')
TX_ID=$(echo "$RESULT" | jq -r '.transactionId')
echo "Sent! Invoice: $INVOICE_ID, Transaction: $TX_ID"