Skip to main content

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.

Requires the connectors:manage permission โ€” most API keys do not have it

Every 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.

ValueSystem
xeroXero
quickbooksQuickBooks
odooOdoo
zohoZoho Books
zuoraZuora
oracle_ebsOracle E-Business Suite
tallyTally
sapSAP
apiDirect API integration
customAnything 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
This is an upsert, not a create

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.

FieldRequiredNotes
connector_typeYesOne of the nine values above. Anything else is HTTP 400.
display_nameNoFree text, maximum 120 characters.
configNoNon-secret settings object โ€” see below.
api_key_idNoAn 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โ€‹

FieldMeaning
idConnector UUID.
connector_typeOne of the nine values above.
statusconfigured, active or disabled.
display_nameYour label, or null.
configNon-secret settings. Returned to callers โ€” see the warning below.
api_key_id / api_key_prefixThe API key tagged to this connector, if any. Only the prefix is shown.
last_seen_atLast time traffic was attributed to this connector.
last_document_atLast document handled.
health_statusDerived at read time โ€” see below. Not stored.
last_errorThe most recent operator-readable error, or null.
needs_attentionSorted list of parked invoice names โ€” see below.
created_at / updated_atTimestamps.
poll_interval_secondsThe 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.

BucketCondition
disabledstatus is disabled.
errorError count above zero.
unknownNever seen โ€” just registered, or no attributed traffic yet.
healthySeen within the last 24 hours.
degradedSeen within 7 days, but not in the last 24 hours.
staleNot 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 returned to callers

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 connector
  • DELETE /api/v1/connectors/{connector_id} โ€” remove it
  • POST /api/v1/connectors/{connector_id}/connect โ€” hand over credentials
  • POST /api/v1/connectors/{connector_id}/disconnect โ€” forget them
  • POST /api/v1/connectors/{connector_id}/test โ€” confirm we can reach the system
  • POST /api/v1/connectors/{connector_id}/sync โ€” pull now rather than wait
  • POST /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:

EndpointPurpose
GET /api/v1/connectors/zoho/oauth/configWhether the one-click button can be offered. Returns {"configured": true|false} and no secrets
POST /api/v1/connectors/zoho/oauth/startBegin the one-click flow; returns the Zoho authorize URL
GET /api/v1/connectors/zoho/oauth/callbackWhere Zoho redirects back. Authenticated by a signed state token, not an API key
POST /api/v1/connectors/{connector_id}/zoho/organizationsList the Zoho Books organizations the grant can see, so you can pick one
POST /api/v1/connectors/zoho/exchangeSelf-Client route: exchange a grant token when you are not using the one-click flow
GET /api/v1/connectors/{connector_id}/zoho/webhook-urlThe 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.