Connector API
A connector is one integration your organization runs โ an ERP or accounts-payable system that exchanges documents with GoRoute. The connector registry lets you register the connectors you operate and read their health.
There is exactly one row per (organization, connector type). Registering the same type twice updates the existing row rather than creating a second one.
connectors:manage permission โ most API keys do not have itEvery endpoint on this page is gated by connectors:manage.
For an API key, that permission is granted only by the admin scope (and the legacy
all scope). The read, write, send and integrate scopes do not include it, so a
key with ordinary integration scopes receives HTTP 403 on every call here.
For a signed-in user, the org_owner, org_admin and developer roles hold it.
If you are getting 403 from these endpoints with a working API key, the key's scope is the reason โ not the key, and not your organization.
A refused call returns HTTP 403 with this body:
{
"error": "forbidden",
"error_code": "INSUFFICIENT_PERMISSION",
"message": "This action requires the 'connectors:manage' permission."
}
Connector typesโ
connector_type must be one of these ten values. Any other value is rejected with
HTTP 400.
| Value | System |
|---|---|
xero | Xero |
quickbooks | QuickBooks |
odoo | Odoo |
zoho | Zoho Books |
zuora | Zuora |
oracle_ebs | Oracle E-Business Suite |
tally | Tally |
sap | SAP |
api | Direct API integration |
custom | Anything else you operate |
List your connectorsโ
GET /api/v1/connectors
Returns the connectors belonging to the calling organization, ordered by
connector_type, each with its derived health.
curl https://app.goroute.ai/peppol-api/api/v1/connectors \
-H "X-API-Key: your_api_key"
The response is an object with a single connectors array:
{
"connectors": [
{
"id": "3f7c1b9e-2a44-4d31-9d0e-8b1f2c6a55d1",
"connector_type": "odoo",
"status": "active",
"display_name": "Odoo Online (production)",
"config": {
"environment": "production"
},
"api_key_id": "c1d2e3f4-5678-4abc-9def-0123456789ab",
"api_key_prefix": "gr_live_ab12",
"last_seen_at": "2026-08-16T09:12:44+00:00",
"last_document_at": "2026-08-16T09:12:44+00:00",
"health_status": "healthy",
"last_error": null,
"needs_attention": [],
"created_at": "2026-05-02T11:00:00+00:00",
"updated_at": "2026-08-16T09:12:44+00:00",
"poll_interval_seconds": 300
}
]
}
Register or update a connectorโ
POST /api/v1/connectors
POST is idempotent on (organization, connector type). Sending it for a
connector_type you already have updates that connector โ it does not create a second
one, and it does not return a conflict. If you expected create semantics, this will
surprise you.
Only the fields you send are changed; omitted fields are left as they are.
| Field | Required | Notes |
|---|---|---|
connector_type | Yes | One of the nine values above. Anything else is HTTP 400. |
display_name | No | Free text, maximum 120 characters. |
config | No | Non-secret settings object โ see below. |
api_key_id | No | An API key belonging to your organization. A key from another organization is HTTP 400. |
curl -X POST https://app.goroute.ai/peppol-api/api/v1/connectors \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"connector_type": "odoo",
"display_name": "Odoo Online (production)",
"config": {"environment": "production"}
}'
The response is the single connector object, in the same shape as the list entries.
Response fieldsโ
| Field | Meaning |
|---|---|
id | Connector UUID. |
connector_type | One of the nine values above. |
status | configured, active or disabled. |
display_name | Your label, or null. |
config | Non-secret settings. Returned to callers โ see the warning below. |
api_key_id / api_key_prefix | The API key tagged to this connector, if any. Only the prefix is shown. |
last_seen_at | Last time traffic was attributed to this connector. |
last_document_at | Last document handled. |
health_status | Derived at read time โ see below. Not stored. |
last_error | The most recent operator-readable error, or null. |
needs_attention | Sorted list of parked invoice names โ see below. |
created_at / updated_at | Timestamps. |
poll_interval_seconds | The cadence the poller actually runs at, reported rather than assumed. |
needs_attention is populated by the Odoo pollerโ
needs_attention lists invoices that were skipped because only a person can fix them โ
for example a rejected invoice that would otherwise block the queue. It is the sorted list
of keys held in the connector's parked-invoice register.
That register is written by the Odoo poller. It is not a generic mechanism: connector
types that are not polled by it will normally show an empty needs_attention, and an empty
list should not be read as "nothing is wrong" for those types.
last_error is deliberately not cleared on the next quiet cycle, so the record that an
invoice was dropped survives.
Derived healthโ
health_status is computed when you read it, from status and the freshness
timestamps. It is not a stored column, so it cannot drift out of sync.
| Bucket | Condition |
|---|---|
disabled | status is disabled. |
error | Error count above zero. |
unknown | Never seen โ just registered, or no attributed traffic yet. |
healthy | Seen within the last 24 hours. |
degraded | Seen within 7 days, but not in the last 24 hours. |
stale | Not seen for more than 7 days. |
The thresholds are deliberately generous: a connector that is idle but fine should not
alarm. Show last_seen_at next to the badge so a person can judge borderline cases.
config is not for secretsโ
config is a non-secret summary โ environment, external company or tenant id, webhook
source and similar. It is serialised back on every read of this endpoint. Do not put
credentials in it.
Credentials for connectors that must be polled are sent separately, through the
connector connect endpoint. They are stored encrypted and are never returned by the
API.
Lifecycle endpointsโ
These are gated by the same connectors:manage permission. Only pull connectors
(odoo, sap, zoho) have credentials to connect, so /connect returns 400 on a push
connector โ see Connectors.
PATCH /api/v1/connectors/{connector_id}โ update a connectorDELETE /api/v1/connectors/{connector_id}โ remove itPOST /api/v1/connectors/{connector_id}/connectโ hand over credentialsPOST /api/v1/connectors/{connector_id}/disconnectโ forget themPOST /api/v1/connectors/{connector_id}/testโ confirm we can reach the systemPOST /api/v1/connectors/{connector_id}/syncโ pull now rather than waitPOST /api/v1/connectors/{connector_id}/documents/{doc_id}/sendโ SAP, send one document
Zoho Books endpointsโ
Zoho connects over OAuth, so it carries its own endpoints alongside the shared ones:
| Endpoint | Purpose |
|---|---|
GET /api/v1/connectors/zoho/oauth/config | Whether the one-click button can be offered. Returns {"configured": true|false} and no secrets |
POST /api/v1/connectors/zoho/oauth/start | Begin the one-click flow; returns the Zoho authorize URL |
GET /api/v1/connectors/zoho/oauth/callback | Where Zoho redirects back. Authenticated by a signed state token, not an API key |
POST /api/v1/connectors/{connector_id}/zoho/organizations | List the Zoho Books organizations the grant can see, so you can pick one |
POST /api/v1/connectors/zoho/exchange | Self-Client route: exchange a grant token when you are not using the one-click flow |
GET /api/v1/connectors/{connector_id}/zoho/webhook-url | The URL to register in Zoho so an invoice reaches the network without waiting for the next poll |
POST /api/v1/connectors/zoho/webhook/{connector_id} | The doorbell Zoho calls. Token-gated; the body is ignored and the poller re-reads from Zoho |
Full request and response schemas are in the API Reference.
Relatedโ
- Organizations โ the tenant a connector belongs to
- Authentication โ API keys and scopes
- Connectors โ pull vs push, and the per-system setup