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, with one exception: held invoices is gated by invoices:read instead, so that the person who has to correct a stuck invoice can read it without being an administrator.

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 twelve values. Any other value is rejected with HTTP 400.

ValueSystem
xeroXero
quickbooksQuickBooks
odooOdoo
zohoZoho Books
bolbol.com marketplace (Retailer API)
amazonAmazon seller account (Selling Partner API)
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": "pk_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 top-level fields you send are changed; omitted top-level fields are left as they are. config is the exception โ€” on POST, if you send it at all, it replaces the stored object entirely. To change one setting, use PATCH, which merges. See how config is written.

FieldRequiredNotes
connector_typeYesOne of the ten values above. Anything else is HTTP 400.
display_nameNoFree text, maximum 120 characters.
configNoSettings object. On POST, replaces the stored object in full โ€” 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 ten values above.
statusconfigured, active or disabled.
display_nameYour label, or null.
configSettings object. Returned to callers in full โ€” see the warnings 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_errorOne line of text, usually summarising every invoice currently parked and who has to fix them โ€” but not always. Read it together with the next field, and see below. Null when there is nothing to report.
last_error_is_held_summarytrue when last_error only restates the parked list; false when it is something else, such as a connection that could not be made โ€” see last_error is not always about invoices. On both the production and test services since 22 September 2026; the availability note in that section has the dates.
needs_attentionSorted list of parked invoice names, and nothing else about them โ€” see below.
held_invoicesThe same parked invoices, but each with its reason and who has to fix it โ€” see Held invoices, with reasons. On both the production and test services since 22 September 2026; the availability note in that section has the dates.
created_at / updated_atTimestamps.
poll_interval_secondsThe cadence the poller actually runs at, reported rather than assumed.

needs_attention โ€” the invoices a person has to unstickโ€‹

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.

Both pollers write that register: Odoo and Zoho Books. It is still not a universal mechanism โ€” a connector type nobody polls has nothing to park, so it will always show an empty needs_attention, and for those types an empty list should not be read as "nothing is wrong".

What to do about a parked invoice, and the difference between Odoo and Zoho Books in how it comes back, is on Connectors โ†’ When an invoice cannot be sent.

last_error โ€” a summary of what is parked, and whose fault it isโ€‹

last_error is not the last thing that went wrong. It is rebuilt on every poll from the whole parked register, so it describes everything currently parked, not only the most recent one. Both pollers, Odoo and Zoho Books, build it the same way.

The part worth reading is that it says who can fix it:

  • A fault the customer has to fix reads "<invoice> was not accepted: <reason> Correct it in your accounting system and it will be picked up automatically."
  • A fault GoRoute has to fix reads "<invoice> could not be processed because of an error on GoRoute's side: <reason> We retry it automatically โ€” no action is needed in Odoo." That sentence names Odoo whichever connector it came from, including Zoho Books; read it as "no action is needed in your accounting system".
  • With several parked at once it names up to four invoices, counts the rest, and โ€” when the two kinds are mixed โ€” says how many of them failed on GoRoute's side and retry automatically.

Do not tell a customer to go and correct invoices that the line says are waiting on us.

The line is capped at 500 characters, so a long reason behind many parked invoices is cut short; needs_attention always has the full list of names.

last_error is still not a permanent record. Once the register is empty and a cycle passes with no failure, both pollers set it back to null and reset the error count. The record that survives is needs_attention: an invoice stays listed there until it is either sent or superseded.

last_error is not always about invoicesโ€‹

Everything above describes a poll that reached your accounting system. A poll that could not log in never gets as far as the parked register, so last_error then holds the connection failure instead โ€” text like "Odoo rejected the credentials" rather than a list of invoices.

That is the trap: the line still reads like an invoice problem, and a person who treats it as one can spend an afternoon correcting invoices that were never the cause.

last_error_is_held_summary is the field that tells the two apart.

last_errorlast_error_is_held_summaryWhat it means
SettrueThe line restates the parked list. Correcting those invoices is the remedy.
SetfalseThe line is about something else โ€” most often that GoRoute could not reach or log in to your system. Correcting invoices will not help. Fix the connection first.
nullfalseNothing to report.

The field is deliberately cautious: GoRoute answers true only when last_error is character-for-character the summary it builds from the parked register. Anything it cannot be sure about answers false, which shows you the message rather than hiding it. So a false on a connector that is otherwise fine costs you a second look; it never costs you a hidden outage.

Availability โ€” on both services, checked 23 September 2026

last_error_is_held_summary is on GoRoute's production service and on its test service. It reached test on 20 September 2026 and production on 22 September 2026, when production was upgraded to the build test was already running.

You can still check rather than trust this page: call GET /api/v1/connectors on the service you use and look for last_error_is_held_summary on a connector.

Run a sync now, and read what it didโ€‹

POST /api/v1/connectors/{connector_id}/sync

Runs one poll cycle immediately instead of waiting for the schedule, and returns what that cycle did. It needs connectors:manage, like the rest of this page, and it returns HTTP 400 if the connector is not connected.

FieldMeaning
fetchedInvoices read from your accounting system this cycle.
submittedInvoices sent onward to the network.
failedInvoices that could not be submitted.
unreachable1 when the cycle could not reach your system at all, otherwise 0.
last_errorThe connector's last_error after the cycle โ€” read it with last_error_is_held_summary above.
connectorThe whole connector object, in the same shape as a list entry.
failed: 0 does not mean the sync worked

failed counts invoices, so a cycle that never logged in has no invoices to count and comes back {"fetched": 0, "submitted": 0, "failed": 0} โ€” byte for byte what an idle, healthy connector returns. That is exactly what a customer saw for four hours on 20 September 2026 while nothing at all was being sent.

unreachable is the field that separates the two. unreachable: 1 with zeros everywhere else means the cycle never got in, not that there was nothing new.

A cycle counts as unreachable in four situations: the stored credentials cannot be read, the connector is sitting out a wait after earlier failures (below), the stored address is one GoRoute refuses to dial, or the connection itself failed.

Availability โ€” on both services, checked 23 September 2026

unreachable is on GoRoute's production service and on its test service. It reached test on 20 September 2026 and production on 22 September 2026, when production was upgraded to the build test was already running. Call the endpoint on your own service and see whether the key comes back. Zoho Books connectors return 0 here; the counter is written by the Odoo poll.

After repeated failures, GoRoute waits before dialling againโ€‹

A connector whose credentials have stopped working used to be re-dialled on every cycle, for as long as it stayed broken. It now backs off: after the first failed connection GoRoute waits 30 seconds before trying again, and doubles that wait with each further failure, up to a ceiling of ten minutes.

What an operator needs to know about it is short:

  • The ordinary recovery never waits. Pressing Connect or Test โ€” or calling POST /api/v1/connectors/{connector_id}/connect or .../test โ€” clears the wait immediately, because both of those prove the connection before they return. Disconnecting clears it too. So pasting a working credential and pressing Connect takes effect at once.
  • Connect and Test also clear last_error and the error count, so the health badge turns from red as soon as the connection is proved, rather than at the next successful poll.
  • While the wait is running, cycles still report unreachable: 1. A connector is no healthier for not having been dialled, so a skipped cycle is reported rather than shown as idle.
  • Anything GoRoute is unsure about resolves towards dialling, not towards waiting. A connector that has been fixed is never held down by a wait it cannot see.
Availability โ€” on both services, checked 23 September 2026

The wait is on GoRoute's production service and on its test service. It reached test on 20 September 2026 and production on 22 September 2026, when production was upgraded to the build test was already running. There is no field that reports it directly; the way to tell is that connect_backoff appears in the connector's config โ€” see the table under how config is written.

Held invoices, with reasonsโ€‹

GET /api/v1/connectors/held-invoices

One entry per invoice your connectors are currently holding, across all of your connectors, each carrying the reason it was held. This is the read side of the same register that needs_attention lists by name: the names told you which invoices were stuck, and never why.

Availability โ€” on both services, checked 23 September 2026

This call, and the held_invoices field on GET /api/v1/connectors, are on GoRoute's production service (app.goroute.ai) and on its test service (app-test.goroute.ai). They reached test on 19 September 2026 and production on 22 September 2026, when production was upgraded to the build test was already running.

On 19 September 2026 production still answered 404 here; on 23 September 2026 it answers 401, the same as GET /api/v1/connectors on that host, which is the answer a real address gives to a caller with no key.

Rather than trust this page, call the address on the service you use. A 404 would mean your service does not have it: fall back to needs_attention and last_error, which are on both.

Permissionโ€‹

This one call needs invoices:read, not the connectors:manage that the rest of this page needs. That is deliberate. The connector page is an administrator's page, but the person who can actually correct a held invoice works in finance โ€” and a held invoice never becomes a transaction or a draft, so without this call there is no screen on which that person can learn that an invoice they raised went nowhere, or why.

  • Signed-in users: every organization role holds invoices:read โ€” org_owner, org_admin, finance, operations, developer and viewer.
  • API keys: the read, write, admin and legacy all scopes include it. The send and integrate scopes do not, so a key with only those scopes gets HTTP 403.
curl https://app.goroute.ai/peppol-api/api/v1/connectors/held-invoices \
-H "X-API-Key: your_api_key"

Responseโ€‹

{
"held_invoices": [
{
"name": "INV/2026/00042",
"reason": "VATEX-OM-01 is an exemption code but the tax is zero-rated.",
"kind": "customer",
"first_failed_at": "2026-09-18T07:14:02.481913+00:00",
"last_tried_at": "2026-09-19T06:02:11.903244+00:00",
"document": "invoice",
"customer": "Muscat Horizon Trading LLC",
"invoice_date": "2026-09-17",
"amount_total": 819.0,
"currency": "OMR",
"source_url": "https://mycompany.odoo.com/web#id=1042&model=account.move&view_type=form",
"source_note": "posted",
"connector_id": "3f7c1b9e-2a44-4d31-9d0e-8b1f2c6a55d1",
"connector_type": "odoo"
}
],
"total": 1
}

Entries come back oldest failure first, so the invoice that has been stuck longest is at the top. total is the number of entries in the list.

FieldMeaning
nameThe invoice number as your accounting system holds it. The one field that is always present.
reasonWhy it was held, in the words the sender was given. null when the entry was written by a release that did not record one โ€” reported as missing rather than guessed at.
kindWho has to fix it. customer โ€” the remedy is in your accounting system, and the invoice is re-presented by itself once you correct it. platform โ€” GoRoute's code failed, and nothing you change will help. Entries written before this field existed read customer.
first_failed_atWhen it was first held. This is the "how long has this been stuck" field; it is not overwritten on a retry.
last_tried_atWhen it was last attempted.
documentinvoice or credit_note.
customerThe buyer named on it.
invoice_dateThe invoice date.
amount_totalThe gross total, as a number.
currencyThe currency as your accounting system names it.
source_urlA link that opens the invoice where it was written. Odoo only, and only when the stored address plainly begins https:// or http://; otherwise null.
source_noteWhether the reason also reached the invoice in the source system: posted, failed, or null when that was never attempted. failed matters โ€” it means this list is the only place the reason can be read.
connector_id / connector_typeWhich of your connectors is holding it.

Fields other than name may be absent on older entries: the register has grown a field at a time since August 2026, and rows written by earlier releases are still in it.

What to do about a held invoice, and how Odoo and Zoho Books differ in bringing it back, is on Connectors โ†’ When an invoice cannot be sent.

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 holds settings rather than credentials โ€” 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.

How config is written: POST replaces, PATCH mergesโ€‹

GoRoute keeps its own working state in config, alongside your settings (the table below). So how a call writes config decides whether a well-formed call that sets one setting also destroys state you never knew was there.

  • PATCH /api/v1/connectors/{connector_id} merges. Each key you send is set to the value you send, replacing that key's whole value. A key you send as null is removed. Every key you do not send is kept as it is. So a PATCH that sets one key changes only that key.
  • POST /api/v1/connectors replaces. Registering a connector type you already have, with config in the body, assigns the config you send over the stored object. Anything that was in config and is not in your request is gone. Use PATCH to change a setting.
This changed on 24 September 2026 โ€” PATCH used to replace too

Until 24 September 2026, PATCH assigned config over the stored object exactly as POST does, and these pages told you to read the whole object and send it back. That still works and gives the same result, because every key you send back is set to what you read. Two things differ:

  • To remove a key, send it as null. Leaving it out keeps it.
  • {"config": {}} changes nothing. It used to clear the whole object.

The merge happens in the database, so a poll that records something into config while your call is on its way keeps what it recorded. It works the other way too: a poll cycle (Odoo, Zoho Books or bol.) writes back only its own keys, merged the same way, so a PATCH that lands while a cycle is running is not put back by it. For Zoho Books this holds from the build that shipped it on 24 September 2026; before it, a Zoho cycle that was running when your PATCH arrived could restore the value it had loaded.

Check which behaviour your service has before relying on a single-key PATCH: the change reaches a service when it is deployed there. Send a PATCH with one harmless key to a connector that holds parked_invoices or another GoRoute key, and read config back. If the other keys are still there, you have the merge.

Keys GoRoute writes into config itself:

KeyWhat it isWhat losing it costs
parked_invoicesThe register behind needs_attentionEvery stuck invoice stops being reported, and nothing brings it back
pending_oauthAn in-flight Zoho authorisation, held between the OAuth callback and the connect callThe authorisation is abandoned; the customer has to start the Zoho connection again
smp_holdA hold set when the sender is not registered on the networkThe hold is lost and sending is retried against a registration that is not there
cn_cursorHow far the Zoho credit-note poll has readCredit notes are re-read from the beginning
evidence_blockedA flag on the Zoho evidence write-backThe write-back state is reset
sync_cursor_handledThe Odoo invoices already handled around the poll's bookmarkInvoices around the bookmark are read again, and may be offered a second time
connect_backoffHow many connections in a row have failed, and when the last one did โ€” see the waitThe wait is forgotten and dialling restarts at full rate. Harmless in itself, and a disconnect clears it deliberately
reference_fieldsThe one key in this table you write, not GoRoute โ€” which of your own Odoo fields carry the contract, project and PO references, see belowYour mapping is gone and invoices go out with the sales order alone, silently. It has an endpoint of its own, which checks the mapping

sync_cursor_handled, connect_backoff and reference_fields are on both the production and test services. The first two reached test on 20 September 2026 and reference_fields on 22 September 2026; all three reached production on 22 September 2026, when production was upgraded to the build test was already running. Read your own connector's config to see which keys your service writes; the rule below does not depend on knowing them all, which is the point of it.

Changing a settingโ€‹

curl -X PATCH https://app.goroute.ai/peppol-api/api/v1/connectors/CONNECTOR_ID \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"config": {"oman_export_zero_rate_code": "VATZR-OM-10"}}'

To remove a setting, send it as null:

curl -X PATCH https://app.goroute.ai/peppol-api/api/v1/connectors/CONNECTOR_ID \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"config": {"oman_export_zero_rate_code": null}}'

Never send config to POST /api/v1/connectors for a connector you already have, and never send one of the keys in the table above in a PATCH unless you mean to overwrite GoRoute's own record: a PATCH of {"config": {"parked_invoices": {}}} still empties the list of stuck invoices.

Odoo: which of your fields carry the contract, project and PO referencesโ€‹

The Odoo connector sends each invoice's sales order (Odoo's Source Document, invoice_origin) as the seller's order reference, with no setting. Odoo has no standard field for a contract, a project or the customer's purchase order on an invoice, so if you keep one in a field of your own you can tell the connector which it is:

TargetOn the e-invoiceExample field
contract_referenceContract reference (IBT-012)contract_id
project_referenceProject reference (IBT-011)x_studio_project
order_referencePurchase order reference (IBT-013)x_customer_po

Set it with its own endpoint, not with PATCH โ€” this one changes the one setting and leaves the rest of config exactly as it is:

PUT /api/v1/connectors/{connector_id}/reference-fields
curl -X PUT https://app.goroute.ai/peppol-api/api/v1/connectors/CONNECTOR_ID/reference-fields \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"contract_reference": "contract_id"}'
  • The body names the field on Odoo's invoice (account.move), lower case. Only the three targets above are accepted; anything else is refused with 422. Send {} to remove the setting.
  • The field must be a text field or a link to another record โ€” a link is sent as the record's name, as Odoo shows it. Any other kind of field, a field that does not exist, or one the connector's Odoo user cannot read is skipped, with a warning in GoRoute's log. The invoice is still sent, without that reference. A setting can never hold an invoice.
  • Whatever you map is written into the e-invoice and filed with the Tax Authority, so a field that is a secret or names a person is refused with 400, and never read even if it is stored some other way: the portal's access_token and access_url, any field whose name contains token, password or secret, and fields about people โ€” partner_id, commercial_partner_id, invoice_user_id, create_uid, anything named for an email, phone, bank or contact. When the connector reads the field it also checks what Odoo says of it, whatever the field is called:
    • A link is sent as the name of the record it links to, so a link to a person, a bank account, a message, a secret or an Odoo setting (res.partner, res.users, res.partner.bank, hr.employee, resource.resource, website.visitor, โ€ฆ) is skipped โ€” however Odoo fills the link in.
    • A text field is sent as itself, so it must hold the invoice's own text. A text field Odoo fills in from somewhere else is skipped: a related field (say one showing the salesperson's login) or a computed one. Map the field it comes from instead โ€” a link is sent as its record's name.
  • It applies from the next poll on. An invoice already sent keeps what it was sent with.
  • Only the Odoo connector reads it (400 on any other type).
  • It is safe to set while a poll is running: the poller writes back only its own bookkeeping, never the whole config, so it cannot put the old value back.
Availability โ€” on both services, checked 23 September 2026

This call is on GoRoute's production service and on its test service. It reached test on 22 September 2026 and production later the same day, on 22 September 2026 at 22:05 UTC, when production was upgraded to the build test was already running.

Test it yourself rather than trusting this page: send the PUT above to the service you use, with your API key. Anything other than 405 Method Not Allowed means the service has the call and is answering you about your key, your connector id or your body โ€” a 401 means the key, a 404 means the connector id, a 400 or 422 means the body. A 405 would mean your service does not have this call, because that is the same answer it gives for an address that does not exist at all. Both services answered 401 when this page was last checked.

Lifecycle endpointsโ€‹

These are gated by the same connectors:manage permission. Only pull connectors (odoo, sap, zoho, bol, amazon) 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; what it returns is under Run a sync now
  • 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. See below
POST /api/v1/connectors/{connector_id}/zoho/webhook-url/rotateIssue a new URL and stop every URL issued before. See below
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.

Reading and rotating the Zoho webhook URLโ€‹

Both calls answer with the same two fields:

{
"url": "https://app.goroute.ai/peppol-api/api/v1/connectors/zoho/webhook/<connector_id>?token=<token>",
"rotated": true
}

url is the address to paste into the Zoho Books workflow rule. rotated says whether this connector's URL has ever been rotated: GET reports false until the first rotation and true afterwards, and the rotate call always answers true.

The URL is a credential. The token is in it, and whoever holds the URL can make GoRoute pull from Zoho and send for that connector, with nothing else to prove who they are. Treat it as you would a password: do not put it in a ticket, a chat message or a screenshot.

Only the organisation itself can read or rotate it. Both calls require the connectors:manage permission, and both additionally refuse any session that is acting on behalf of another organisation โ€” a partner working in your account, however much access they have. The refusal is 403 with error_code ACT_AS_READ_ONLY. The code name says read-only, but the check does not look at the access level: every act-as session is refused, not only a read-only one.

GET is the method that makes this matter. Without the extra check, a partner who can see your connectors at all could read a live credential of an account they may only look at.

What else is refused. A connector that is not a Zoho connector gets 400 with the message Not a Zoho connector.; a connector id that does not belong to your organisation gets 404 "Connector not found".

After a rotation, update Zoho. Every URL handed out before the rotation stops working the moment the rotate call returns, so the Zoho Books workflow rule has to be updated with the new URL. Until it is, invoices still reach GoRoute on the regular poll โ€” they simply stop arriving instantly. Rotate when a URL may have been seen by someone who should not have had it.

bol.com: the connect bodyโ€‹

bol.com adds no endpoints of its own; it uses the shared lifecycle above. POST /api/v1/connectors/{connector_id}/connect on a bol connector takes:

FieldPurpose
client_id, client_secretRetailer API credentials from the bol seller dashboard (Settings โ†’ Services โ†’ API settings). Proven against bol before anything is saved; stored encrypted, never returned
demotrue points the connector at bol's demo environment (fixed sample orders; uploads are accepted and discarded). Default false
vat_rateThe VAT rate the shop charges, as a percentage. bol gives prices including VAT and no rate. Default 21
invoice_number_prefixWhat precedes the bol order id in the invoice number. Default BOL-

config then carries environment, client_id_prefix, vat_rate and invoice_number_prefix, plus the poller's own registers (bol_awaiting_shipment, bol_uploads_pending, bol_consumer_requests, bol_cursor_handled). Those registers are why the rate and the prefix are not changed through config: to change them, disconnect and connect again rather than PATCH the config (see how config is written). What the poller does with an order is described under bol.com.

Amazon: one-click authorisation and the connect bodyโ€‹

Amazon connects in one click: the portal's Connect with Amazon button asks the API for the consent URL (the amazon/oauth/start action under the connectors resource, taking the country whose Seller Central the seller signs in to, or โ€” when the flow began on the Selling Partner Appstore โ€” Amazon's amazon_callback_uri and amazon_state to be sent back), the seller authorises GoRoute on Amazon's page, and Amazon redirects to the amazon/oauth/callback action with state, selling_partner_id and spapi_oauth_code. The signed state is bound to the organisation that started the flow; the code is exchanged for the refresh token, the seller's stores are discovered and the connector goes active. amazon/oauth/config says whether the platform's application is registered on the service and whether it is still a draft (the consent URL then carries version=beta). All four authorisation addresses are now in the API reference:

CallWhat it is forWho may call it
GET /api/v1/connectors/amazon/oauth/configWhether the platform's Amazon application is registered on this service, whether it is still a draft, which Seller Central countries are offered, and the redirect address. No secrets are returnedconnectors:manage
POST /api/v1/connectors/amazon/oauth/startThe URL to send the browser to. Answers 409 when the platform's application is not registered on this service โ€” paste a refresh token insteadconnectors:manage
GET /api/v1/connectors/amazon/oauth/loginThe application's OAuth Login URI, used when the flow begins on the Selling Partner Appstore rather than in GoRoute. Redirects the seller into the portal, which then calls amazon/oauth/startNobody signs in here โ€” it is a browser redirect Amazon opens
GET /api/v1/connectors/amazon/oauth/callbackWhere Amazon returns with state, selling_partner_id and spapi_oauth_codeAlso a browser redirect; the signed state is the authentication

The fallback is the shared lifecycle: POST /api/v1/connectors/{connector_id}/connect on an amazon connector takes:

FieldPurpose
refresh_tokenThe LWA refresh token from a self-authorisation in Seller Central (Apps & Services โ†’ Manage Your Apps). Proven in three named stages โ€” Amazon's sign-in (lwa), the API (spapi), a VCS store (marketplaces) โ€” before anything is saved; stored encrypted, never returned
regioneu (default), na or fe โ€” the SP-API region of the seller's stores
sandboxtrue points the connector at Amazon's hosted sandbox (static answers; nothing reaches a buyer). Default false
invoice_number_prefixWhat precedes Amazon's shipment id in the invoice number. Default AMZ-

A failed connect or test answers 400 with {"error", "stage", "message"} so the page can say which stage refused. config then carries environment, region, authorised_via (oauth or pasted), selling_partner_id, marketplaces (the VCS stores enabled) and all_marketplaces, plus the poller's own registers (amazon_orders_seen, amazon_uploads_pending, amazon_vidr_report, amazon_cursor_handled, amazon_status) โ€” written back under the same merge as the other pollers. The seller's confirmation that VCS is set to I will upload my own invoices is a merged PATCH of config.amazon_vcs_upload_acknowledged (true, with amazon_vcs_upload_acknowledged_at); there is no endpoint of its own. What the poller does with a shipment is described under Amazon.

Amazon readinessโ€‹

GET /api/v1/connectors/{connector_id}/amazon/readiness needs connectors:manage. It is the call behind the readiness list on the connector page, and it is the only place an integrator can read the same answer.

The call reads what GoRoute already holds โ€” the connector's config, the organisation's own seller record and the SMP. It does not contact Amazon, so it is cheap and safe to poll, and a green answer says GoRoute is ready rather than that Amazon has just confirmed anything.

Two refusals:

  • 400 with Not an Amazon connector. โ€” the connector exists and is yours, but it is some other kind.
  • 404 with Connector not found โ€” the id is unknown, or it belongs to another organisation. The two cases are deliberately indistinguishable.

The generated reference page for this call shows no response fields. That is a gap in the published API description, not in the call: the handler returns a plain object the description cannot express. The fields are these.

FieldWhat it holds
readytrue when every check that counts has passed โ€” see the warning below
checksThe eight checks, in the order the panel shows them. Each is an object with id, ok, label and detail
awaitingHow many Amazon orders the poller has seen and not yet turned into invoices
uploads_pendingHow many issued documents are still waiting to be uploaded to Amazon, or waiting for Amazon's verdict on a feed
lateThe keys of the pending uploads that have passed Amazon's business-day deadline. Empty when nothing is late
last_cycle_atWhen the poller last ran for this connector, or null if it has not run yet
held_invoicesInvoices this connector is holding, each with its reason โ€” the same entries as on GET /api/v1/connectors, described under Held invoices, with reasons

The eight checks, by id:

idlabelPasses when
connectedAmazon authorisedThe connector is active and holds stored credentials. detail then names how it was authorised (oauth or a pasted token) and which environment it points at; otherwise it tells the seller to connect or paste a refresh token
marketplacesStores foundAt least one VCS store was discovered. detail lists their country codes
seller_identitySeller identity completeYour organisation has a seller record and nothing is missing from it. detail names the missing fields, which are filled in under Settings โ†’ Company
seller_registeredSeller registered on the networkThe seller's own Peppol identifier is registered on GoRoute's SMP, or โ€” failing that โ€” still resolves on the network
vcs_acknowledgedVCS set to "I will upload my own invoices"config.amazon_vcs_upload_acknowledged is set. Amazon offers no API for this setting, so it is a confirmation the seller gives after changing it in Seller Central, not something GoRoute can read
buyer_dataBuyer details released by AmazonThe poller has recorded no problem getting buyer details. This check does not affect ready
vidrVAT Invoice Data ReportThe last recorded state of the report is good, or none has been requested yet
on_timeUploads within Amazon's business dayNo pending upload has passed its deadline โ€” the same list as late
ready can be true while a check is failing

ready is computed from seven of the eight checks: buyer_data is excluded. So a connector whose buyer details Amazon has not released still reports ready: true, while the panel shows that check in red. If you are gating anything on readiness, read checks and decide for yourself which failures matter to you rather than trusting ready alone.