Skip to main content

Saved Parties

A saved party is an entry in your organisation's own address book: a customer or supplier you invoice often, stored once with their Peppol identifier, tax number, address, contact and bank details. It saves retyping the same buyer on every invoice, and it gives you something to search when building an autocomplete field in your own interface.

This is not the Peppol participant directory. A Peppol participant is a registered address on the network, published in the SMP and visible to everyone; Participant Management covers those. A saved party is private to your organisation, visible to nobody else, and creating one publishes nothing.

An ordinary API key is enough

Every endpoint on this page authenticates with your organisation's API key and needs no extra permission, which is unusual for endpoints that write data. Anyone holding a key for your organisation can read, add, change and delete address-book entries.

The endpointsโ€‹

All paths are relative to https://app.goroute.ai/peppol-api. {party_id} is the UUID of the saved party.

MethodPathPurpose
GET/api/v1/parties/searchSearch the address book โ€” the autocomplete endpoint
GET/api/v1/parties/frequentThe most-used parties. Takes party_type and a limit between 1 and 20, default 5
GET/api/v1/parties/favoritesEvery party flagged as a favourite, in name order. Takes party_type
POST/api/v1/partiesAdd a party, or record another use of one you already have
GET/api/v1/parties/{party_id}Fetch one party. 404 if it is not yours
PATCH/api/v1/parties/{party_id}Change any stored field. Send only the fields you are changing
DELETE/api/v1/parties/{party_id}Delete a party. Returns 204 with no body
POST/api/v1/parties/{party_id}/favoriteFlip the favourite flag on or off

Add a partyโ€‹

POST /api/v1/parties โ€” returns 201 Created.

import requests

BASE_URL = "https://app.goroute.ai/peppol-api"

response = requests.post(
f"{BASE_URL}/api/v1/parties",
headers={"X-API-Key": "your_api_key", "Content-Type": "application/json"},
json={
"party_type": "buyer",
"peppol_id": {"scheme": "0106", "value": "12345678"},
"name": "My Customer BV",
"vat_number": "NL123456789B01",
"address": {
"street": "Main Street 123",
"city": "Amsterdam",
"postal_code": "1012AB",
"country_code": "NL",
},
"contact": {"name": "Accounts Payable", "email": "ap@customer.nl"},
"is_favorite": True,
},
)

party = response.json()
print(party["id"], party["use_count"])

What the request acceptsโ€‹

FieldRequiredNotes
peppol_id.valueYesThe identifier itself, without the scheme
peppol_id.schemeNoDefaults to 0088
nameYes1 to 255 characters
party_typeNoDefaults to buyer; the other value used by the product is seller
legal_name, registration_number, vat_numberNoPlain strings
addressNostreet, additional, city, postal_code, country_subdivision, country_code
contactNoname, phone, email
bankNoname, iban, bic
is_favoriteNoDefaults to false
bank.payment_reference is accepted and then dropped

The create request will not complain if you send a payment reference inside bank, but it is not stored. The field exists on the record and can be set afterwards with PATCH /api/v1/parties/{party_id}, where it is a top-level payment_reference.

An empty peppol_id.value returns HTTP 400.

Posting the same party twice is not an errorโ€‹

Parties are unique per organisation on the combination of scheme and identifier value. If you post one you already hold, GoRoute does not create a duplicate and does not fail. It:

  • increases use_count by one,
  • sets last_used_at to now,
  • replaces name if the one you sent is different,
  • applies is_favorite if you sent true,
  • and ignores every other field in your request.

The response is still 201. That last point is the one that catches people: a corrected address or a new VAT number sent this way is silently discarded. Use PATCH to change a party you already have.

What comes backโ€‹

FieldMeaning
idThe party's UUID
party_typebuyer or seller
peppol_scheme, peppol_idThe identifier, split into its two halves
name, legal_name, registration_number, vat_numberAs stored
address, contactObjects, always present, with null for anything unset
banknull unless an IBAN is stored
use_countHow many times this party has been posted. Starts at 1
last_used_atWhen it was last posted, or null
is_favoriteWhether it is flagged as a favourite
verifiedA flag you control yourself through PATCH. GoRoute checks nothing and never sets it
auto_learnedWhether the entry was created by something other than a direct request. false for everything you create here

Search the address bookโ€‹

GET /api/v1/parties/search โ€” the endpoint to put behind an autocomplete field.

response = requests.get(
f"{BASE_URL}/api/v1/parties/search",
params={"q": "customer", "party_type": "buyer", "limit": 10},
headers={"X-API-Key": "your_api_key"},
)

results = response.json()
print(results["count"])
for party in results["parties"]:
print(party["name"], party["peppol_scheme"] + ":" + party["peppol_id"])
ParameterDefaultNotes
qemptyMatched against name, Peppol identifier, legal name and VAT number
party_typenoneOptional filter, buyer or seller
limit10Between 1 and 50

q is a case-insensitive "contains" match, not a prefix match, so route finds GoRoute Holding. Leaving q out returns your address book in the same order, capped at limit โ€” which is what you want for the first keystroke of an autocomplete.

Results come back in a fixed order: favourites first, then the most used, then the most recently used. The response is {"parties": [...], "count": N}, where count is the number of rows in this response, not the size of your address book.

Favouritesโ€‹

POST /api/v1/parties/{party_id}/favorite toggles the flag โ€” it does not set it. Calling it twice puts you back where you started. To set the flag to a known value regardless of its current state, use PATCH with {"is_favorite": true}.

Next Stepsโ€‹