Skip to main content

Authentication

GoRoute uses API keys to authenticate requests. All API requests must include your API key in the X-API-Key header.

API Key Formatโ€‹

GoRoute API keys are environment-prefixed so test and production credentials are never confused:

EnvironmentPrefixExample
Sandbox / testsk_test_sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Production / livepk_live_pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Both are sent the same way, in the X-API-Key header. The examples below use pk_live_your_key_here โ€” substitute your sandbox key (sk_test_...) while testing.

Using Your API Keyโ€‹

Include your API key in every request:

curl -X GET https://app.goroute.ai/peppol-api/api/v1/settings/organization \
-H "X-API-Key: pk_live_your_key_here"

Python Exampleโ€‹

import requests

headers = {
"X-API-Key": "pk_live_your_key_here",
"Content-Type": "application/json"
}

response = requests.get(
"https://app.goroute.ai/peppol-api/api/v1/settings/organization",
headers=headers
)

Node.js Exampleโ€‹

const axios = require('axios');

const response = await axios.get(
'https://app.goroute.ai/peppol-api/api/v1/settings/organization',
{
headers: {
'X-API-Key': 'pk_live_your_key_here'
}
}
);

C# Exampleโ€‹

using System.Net.Http;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "pk_live_your_key_here");

var response = await client.GetAsync(
"https://app.goroute.ai/peppol-api/api/v1/settings/organization"
);

Creating API Keysโ€‹

  1. Log in to the GoRoute Dashboard
  2. Navigate to Settings โ†’ API Keys
  3. Click Create API Key
  4. Give it a descriptive name (e.g., "Production Backend", "CI/CD Pipeline")
  5. Copy the key immediately โ€” it won't be shown again

Managing API Keys Over the APIโ€‹

Keys can also be created, listed, inspected and revoked programmatically. All four endpoints live under /api/v1/api-keys and act on the organization that owns the calling key โ€” you can never see or revoke another organization's keys.

Method and pathWhat it doesSuccess status
POST /api/v1/api-keysCreate a key201
GET /api/v1/api-keysList your organization's keys200
GET /api/v1/api-keys/{api_key_id}Fetch one key by ID200
DELETE /api/v1/api-keys/{api_key_id}Revoke a key permanently204
The secret is returned exactly once

Creating a key is the only response in the whole API that contains the key field. Every other response returns key_prefix โ€” the leading characters, enough to identify the key in a list and useless for authentication. If the secret is lost, revoke the key and create another. There is no endpoint that will show it to you again.

Create a keyโ€‹

FieldTypeRequiredConstraint / default
namestringyes1โ€“100 characters
descriptionstringnoup to 500 characters
scopesarray of stringsnodefaults to ["send", "read"]
rate_limit_per_minuteintegerno1โ€“1000, defaults to 60
rate_limit_per_dayintegerno1โ€“1000000, defaults to 10000
expires_attimestampnoomit or send null for a key that never expires
curl -X POST https://app.goroute.ai/peppol-api/api/v1/api-keys \
-H "X-API-Key: pk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "CI/CD Pipeline",
"description": "Sends invoices from the build server",
"scopes": ["send", "read"],
"rate_limit_per_minute": 100,
"rate_limit_per_day": 50000
}'

The 201 response carries the secret once, alongside the same fields every other key response returns:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "6f1c2f9e-6a1e-4a6f-9a2b-0d5f4c3b2a19",
"key": "pk_live_...",
"key_prefix": "pk_live_acme",
"name": "CI/CD Pipeline",
"description": "Sends invoices from the build server",
"scopes": ["send", "read"],
"rate_limit_per_minute": 100,
"rate_limit_per_day": 50000,
"status": "active",
"last_used_at": null,
"request_count": 0,
"created_at": "2026-08-09T09:00:00Z",
"expires_at": null
}

The per-key rate limits you set here can only ever lower what the key is allowed to do. See Rate Limiting below.

List and inspect keysโ€‹

curl -X GET "https://app.goroute.ai/peppol-api/api/v1/api-keys?page=1&page_size=50&status=active" \
-H "X-API-Key: pk_live_your_key_here"
Query parameterDefaultNotes
page11 or greater
page_size501 to 100
status(none)Filter by active or revoked

Fetch a single key with GET /api/v1/api-keys/{api_key_id}. Neither response contains the secret.

Revoke a keyโ€‹

curl -X DELETE https://app.goroute.ai/peppol-api/api/v1/api-keys/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: pk_live_your_key_here"

A successful revocation returns 204 with no body. Revocation is permanent โ€” a revoked key cannot be reactivated, and every subsequent request made with it fails with 401.

ConditionHTTPerror_code
No such key in your organization404API_KEY_NOT_FOUND
Key is already revoked400API_KEY_ALREADY_REVOKED

Scopesโ€‹

ScopeGrants
sendSend documents via Peppol
readRead transactions, participants and lookups
writeCreate and update participants, delete transactions
adminManage webhooks and API keys

A key holding admin satisfies any scope check. A request whose key lacks the scope an endpoint requires is rejected with 403 and INSUFFICIENT_SCOPE.

Key expiryโ€‹

expires_at is optional and there is no default โ€” a key created without one never expires. Once the timestamp has passed, the key is rejected with 401 and EXPIRED_API_KEY. Expiry is separate from revocation: an expired key still exists and still appears in the list with status active.

About the apikeys:manage permission

All four endpoints declare a required permission, apikeys:manage. Whether that declaration is actually enforced depends on deployment configuration: role-based access control ships in shadow mode by default, in which a missing permission is logged and the call is allowed through. This page states what the endpoints declare. It does not promise that a call without the permission is refused โ€” confirm the behaviour of your own tenant before relying on it as a control.

API Key Best Practicesโ€‹

โœ… Doโ€‹

  • Store keys in environment variables or secret managers
  • Use different keys for development, staging, and production
  • Rotate keys periodically (every 90 days recommended)
  • Use descriptive names to identify key purposes
  • Revoke unused keys promptly

โŒ Don'tโ€‹

  • Commit keys to source control (Git, SVN)
  • Expose keys in client-side code (JavaScript, mobile apps)
  • Share keys via email or chat
  • Use the same key across multiple environments
  • Log API keys in application logs

Environment Variablesโ€‹

Store your API key as an environment variable:

# Linux/macOS
export GOROUTE_API_KEY="pk_live_your_key_here"

# Windows PowerShell
$env:GOROUTE_API_KEY = "pk_live_your_key_here"

# Windows CMD
set GOROUTE_API_KEY=pk_live_your_key_here

Then use it in your code:

import os
API_KEY = os.environ.get("GOROUTE_API_KEY")

Key Rotationโ€‹

To rotate an API key:

  1. Create a new API key โ€” in the dashboard, or with POST /api/v1/api-keys
  2. Update your application to use the new key
  3. Deploy the update
  4. Verify the new key works
  5. Revoke the old key โ€” DELETE /api/v1/api-keys/{api_key_id}

Confirm step 4 before step 5 rather than after: revocation is permanent and cannot be undone. If you want a rotation deadline enforced for you, set expires_at on the outgoing key instead of revoking it by hand.

Zero-Downtime Rotation

GoRoute allows multiple active API keys per organization. Create the new key before revoking the old one to avoid downtime.

Error Responsesโ€‹

Authentication, authorization and rate-limit failures all return a flat JSON object. The four fields sit at the top level of the body:

FieldDescription
errorShort category, such as unauthorized, forbidden or rate_limit_exceeded
error_codeThe specific condition. Branch your code on this, never on the message
messageHuman-readable explanation. The wording may change without notice
request_idCorrelation ID for this request. Quote it when you contact support
Read error_code at the top level

Earlier revisions of this page showed these fields wrapped inside a nested object and gave every authentication failure the same generic code. The API has never returned that shape. A client that reaches into a nested error object, or that distinguishes failures by message text, will break against the real response โ€” read the top-level error_code instead.

Authentication and authorization codesโ€‹

ConditionHTTPerror_code
No X-API-Key header on the request401MISSING_API_KEY
Key is unknown or has been revoked401INVALID_API_KEY
Key is past its expires_at401EXPIRED_API_KEY
Key is valid but lacks the scope the endpoint requires403INSUFFICIENT_SCOPE
Per-minute rate limit exceeded429RATE_LIMIT_MINUTE
Per-day rate limit exceeded429RATE_LIMIT_DAY

A missing key example:

{
"error": "unauthorized",
"error_code": "MISSING_API_KEY",
"message": "API key is required. Include X-API-Key header.",
"request_id": "3f6c1b9a-2f4e-4b8d-9c31-7a2d5e8f0b14"
}
A revoked key and an unknown key look identical

Both return 401 with INVALID_API_KEY and the same message. This is deliberate: the API does not confirm that a key it is refusing ever existed, so a revoked credential cannot be used to probe the platform. Do not build retry or alerting logic that expects to tell the two apart โ€” check the key's status through GET /api/v1/api-keys instead.

Rate Limitingโ€‹

Rate limits are applied per organization, not per key. Two keys belonging to the same organization draw down the same per-minute and per-day budget.

Each organization's ceiling comes from its billing plan:

PlanRequests/minuteRequests/day
free10100
starter601,000
professional1205,000
business30020,000
enterprise1,000100,000
smp_only30500
developer_sandbox301,000
demo_readonly30500

An individual key carries its own rate_limit_per_minute and rate_limit_per_day. A key limit can only lower the plan limit, never raise it โ€” the effective limit is the lower of the two. A key created on the starter plan with rate_limit_per_minute set to 500 is still limited to 60 requests a minute; setting a key limit above your plan has no effect at all.

Every response carries the current rate-limit state, and a 429 adds Retry-After:

HeaderMeaning
Retry-AfterSeconds until the exceeded window resets
X-RateLimit-LimitThe effective limit for the window that was exceeded
X-RateLimit-RemainingRequests left in the window โ€” 0 on a 429
X-RateLimit-ResetUnix timestamp at which the window resets

A rate-limited response also repeats the wait in the body as retry_after, in seconds:

{
"error": "rate_limit_exceeded",
"error_code": "RATE_LIMIT_MINUTE",
"message": "Rate limit exceeded. Limit: 60/minute",
"retry_after": 42,
"request_id": "3f6c1b9a-2f4e-4b8d-9c31-7a2d5e8f0b14"
}

Back off using Retry-After rather than retrying on a fixed interval, and check error_code to see whether you have hit the per-minute window โ€” which clears in under a minute โ€” or the per-day one, which does not.

See Rate Limits for more details.

Audit Logsโ€‹

The platform records, for each API key:

  • the timestamp of its last use (last_used_at)
  • the source IP address of the most recent request
  • a cumulative request count (request_count)

last_used_at and request_count are returned by GET /api/v1/api-keys and by GET /api/v1/api-keys/{api_key_id}, so you can identify a key that has stopped being used โ€” or one that is being used when it should not be โ€” before you revoke it.

Next Stepsโ€‹