Skip to main content

Send Invoice — curl Examples

Complete examples for looking up recipients, sending invoices, handling idempotency, and dealing with errors.


1. Look up a recipient

Check if a Peppol participant is registered and get their routing info.

curl https://api.invostaq.com/api/v1/lookup?participantId=0196:971501234567 \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm"
{
"participantId": "0196:971501234567",
"isRegistered": true,
"accessPointRef": "AP-0196-971501234567",
"supportedDocTypes": [
"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
],
"provider": "Invostaq"
}

If isRegistered is false, the recipient can't receive Peppol invoices.


2. Send an invoice

Use the accessPointRef from the lookup response.

curl -X POST https://api.invostaq.com/api/v1/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-001-send" \
-d '{
"senderParticipantId": "0196:971500000001",
"receiverParticipantId": "0196:971501234567",
"accessPointRef": "AP-0196-971501234567",
"extracted": {
"invoiceNumber": "INV-2024-001",
"issueDate": "2024-06-15T00:00:00Z",
"currencyCode": "AED",
"totalAmount": 1050.00,
"totalTaxAmount": 50.00,
"vendor": {
"name": "Acme Trading LLC",
"taxId": "123456789012345",
"address": "Dubai, UAE"
},
"customerName": "Gulf Imports Co",
"lineItems": [
{
"description": "Consulting services — June 2024",
"quantity": 1,
"unitPrice": 1000.00,
"vatRate": 0.05
}
]
}
}'
{
"status": "success",
"transactionId": "txn_abc123def456",
"databaseRecordId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provider": "Invostaq"
}

The invoice status starts as Processing. You'll receive a webhook when it changes to Delivered or Failed.


3. Multiple line items

curl -X POST https://api.invostaq.com/api/v1/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-002-send" \
-d '{
"senderParticipantId": "0196:971500000001",
"receiverParticipantId": "0196:971501234567",
"accessPointRef": "AP-0196-971501234567",
"extracted": {
"invoiceNumber": "INV-2024-002",
"issueDate": "2024-06-20T00:00:00Z",
"currencyCode": "AED",
"totalAmount": 3360.00,
"totalTaxAmount": 160.00,
"vendor": {
"name": "Acme Trading LLC",
"taxId": "123456789012345",
"address": "Dubai, UAE"
},
"lineItems": [
{
"description": "Office chairs (ergonomic)",
"quantity": 5,
"unitPrice": 500.00,
"vatRate": 0.05,
"productCode": "FURN-CHAIR-ERG"
},
{
"description": "Standing desk converter",
"quantity": 2,
"unitPrice": 350.00,
"vatRate": 0.05,
"productCode": "FURN-DESK-STD"
}
]
}
}'

Totals breakdown: Line 1: 5 x 500 = 2500.00, tax 125.00, subtotal 2625.00. Line 2: 2 x 350 = 700.00, tax 35.00, subtotal 735.00. Grand total: 3360.00. Tax total: 160.00.


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.invostaq.com/api/v1/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-001-send" \
-d '{ ... same invoice body ... }'

# Retry with same key — safe, returns original result
curl -X POST https://api.invostaq.com/api/v1/invoices/send \
-H "x-api-key: sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-2024-001-send" \
-d '{ ... same invoice body ... }'

Both requests return the same transactionId and databaseRecordId. The invoice is only sent once.

Key rules:

  • Keys can be any string up to 128 characters
  • Good choices: invoice number (INV-2024-001), UUID ($(uuidgen)), or a composite (tenant-inv2024001-v1)
  • Same key + same tenant = idempotent (returns cached result)
  • Different key = new submission

5. Error examples

Missing API key

curl -X POST https://api.invostaq.com/api/v1/invoices/send \
-H "Content-Type: application/json" \
-d '{}'
{
"type": "https://invostaq.com/errors/api-key-required",
"title": "API key required",
"detail": "Include your API key in the x-api-key header.",
"status": 401
}

Totals mismatch

{
"type": "https://invostaq.com/errors/totals-mismatch",
"title": "Invoice totals mismatch",
"detail": "Computed line total (including VAT) is 105.00, but totalAmount is 999.99. Difference exceeds ±0.02 tolerance.",
"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
}

Wait the number of seconds in Retry-After, then retry.

See the full Error Reference for all error types.


6. Complete script: lookup + send

#!/bin/bash
set -euo pipefail

API_KEY="sk_test_dGVzdGtleS0xMjM0NTY3ODkwYWJjZGVm"
BASE="https://api.invostaq.com/api"
RECEIVER="0196:971501234567"

# Step 1: Lookup
echo "Looking up $RECEIVER..."
LOOKUP=$(curl -sf "$BASE/v1/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/v1/invoices/send" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{
\"senderParticipantId\": \"0196:971500000001\",
\"receiverParticipantId\": \"$RECEIVER\",
\"accessPointRef\": \"$AP_REF\",
\"extracted\": {
\"invoiceNumber\": \"INV-2024-001\",
\"issueDate\": \"2024-06-15T00:00:00Z\",
\"currencyCode\": \"AED\",
\"totalAmount\": 1050.00,
\"totalTaxAmount\": 50.00,
\"vendor\": {
\"name\": \"Acme Trading LLC\",
\"taxId\": \"123456789012345\"
},
\"lineItems\": [{
\"description\": \"Consulting services\",
\"quantity\": 1,
\"unitPrice\": 1000.00,
\"vatRate\": 0.05
}]
}
}")

TX_ID=$(echo "$RESULT" | jq -r '.transactionId')
DB_ID=$(echo "$RESULT" | jq -r '.databaseRecordId')

echo "Sent! Transaction: $TX_ID, Record: $DB_ID"