Suppliers
A supplier is a company that invoices a client. It is the "from whom" on every invoice row and on every missing document.
Suppliers are scoped to the client company, not to the organization. Two clients that both buy electricity from the same utility hold two supplier rows. One firm's counterparty data never leaks into another client's list, and merging is always within one client.
Separate from that is the supplier directory: a shared, read-only list of companies Syllevo
already knows how to collect invoices from, with setup instructions for each. A supplier row can be
backed by a directory entry, in which case it carries a directorySlug.
Routes
| Method | Path | Needs |
|---|---|---|
| GET | /v1/suppliers |
View, profileId required |
| GET | /v1/suppliers/{id} |
View |
| POST | /v1/suppliers/{id}/merge |
ManageSources |
| GET | /v1/organization/suppliers |
Any member |
| GET | /v1/suppliers/connections |
View, profileId required |
| POST | /v1/suppliers |
ManageSources |
| POST | /v1/suppliers/from-directory |
ManageSources |
| PUT | /v1/suppliers/{id}/connection-methods |
ManageSources |
| GET | /v1/supplier-directory |
Any credential |
| GET | /v1/vat-validations |
View |
| GET | /v1/vat-validations/count |
View |
The supplier shape
| Field | Type | Means |
|---|---|---|
id |
uuid | The supplier row |
profileId |
uuid | The client company it invoices |
name |
string | The supplier's name |
taxIdentifierType |
TaxIdentifierType |
0 VAT number, 1 national registration, 2 other |
taxIdentifierValue |
string or null | The identifier itself, null when none is known |
countryCode |
string or null | Two letters |
email |
string or null | The address invoices arrive from, when one is known |
directorySlug |
string or null | The directory entry behind this row, null for a local one |
GET /v1/suppliers
One page of a client's suppliers, ordered by name. View on the client.
| Parameter | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | yes | The client company |
search |
string | no | Matches anywhere in the name, case-insensitively |
page |
integer | no | Default 1 |
pageSize |
integer | no | Default 25, clamped to 200 |
profileId is required here because this is the door where the client company is the question
being asked. A caller with no claim on it is refused with 403 rather than handed an empty page,
which is the opposite of how the document and review lists behave.
GET /v1/suppliers/{id}
One supplier. View on the client it belongs to. A supplier the caller cannot see answers 404.
GET /v1/organization/suppliers
The same rows, across every client the caller can see, in one request. A firm with three hundred clients would otherwise need three hundred calls to draw one roster.
| Parameter | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | no | Narrow to one client |
search |
string | no | Matches anywhere in the name, case-insensitively |
page |
integer | no | Default 1 |
pageSize |
integer | no | Default 25, clamped to 200 |
Here profileId narrows and never widens, so naming a client the caller cannot see gives an empty
page, not a refusal.
GET /v1/suppliers/connections
A client's suppliers with their connection state: how invoices from each one reach Syllevo today.
View. profileId is required.
| Field | Type | Means |
|---|---|---|
supplier |
object | The supplier shape above |
connectionMethods |
array of SupplierConnectionMethod |
How invoices can arrive, directory and row together |
senderAddresses |
array of string | Addresses invoices from this supplier come from |
directorySlug |
string or null | The directory entry behind it |
tutorialContentKey |
string or null | The key of the setup instructions, for directory rows |
SupplierConnectionMethod is a flags enum, so the array lists the methods that apply:
| Value | Name | Means |
|---|---|---|
0 |
None | Nothing is connected |
1 |
EmailForwarding | Invoices arrive by mail from a known address |
2 |
EFactura | They arrive through ANAF |
4 |
Connector | A portal connector fetches them |
connectionMethods is the union of three sources: what the directory entry behind this supplier
advertises, what the firm declared on its own supplier row when it added one by hand, and what an
arrival has proven. An invoice downloaded from ANAF puts 2 on the supplier that issued it, because
a route invoices actually travel by is better evidence than a box somebody did or did not tick. None
of the three can remove a method another added, and 1 is always in the list. A supplier that
matches no directory entry answers with whatever the row holds, which is 1 alone until a firm says
otherwise or one of its invoices arrives through e-Factura.
tutorialContentKey names a piece of copy held in the Syllevo web app, not a URL. It is there so
the app can show the right setup steps. A server integration can ignore it.
POST /v1/suppliers/from-directory
Connects a listed supplier to a client company. ManageSources, because this is connecting a
source, not filing a document.
| Field | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | yes | The client company |
slug |
string | yes | The directory entry, from the catalog |
Answers 200 with the connection. Calling it again for a supplier the client already holds finds
the existing row rather than creating a second one, so it is safe to repeat.
This is the route to prefer. A directory-backed supplier arrives with its sender addresses already known, so invoices from it are recognized on the first arrival instead of landing in review.
POST /v1/suppliers
Adds a supplier the directory does not list. ManageSources.
| Field | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | yes | The client company |
name |
string | yes | The supplier's name |
senderAddresses |
array of string | no | Addresses its invoices arrive from |
taxIdentifierType |
TaxIdentifierType |
no | 0 VAT, 1 national registration, 2 other |
taxIdentifierValue |
string | no | The identifier |
countryCode |
string | no | Two letters |
connectionMethods |
array | no | How its invoices arrive |
Fill in countryCode and taxIdentifierValue together if you fill them in at all. A tax
identifier stored without a country is matched differently from one stored with it, and that is how
a company ends up with two rows.
connectionMethods says how the firm expects this supplier's invoices to arrive. Leave it out, or
send an empty array, and the supplier is email forwarding only. Send 2 to say the supplier
invoices through ANAF, so an invoice missing from it is chased on e-Factura instead of by mail.
1 is accepted and redundant: email forwarding is always included whether or not it is listed.
0 and 4 are refused with 400 - 0 is not a route, and a connector needs a slug only the
directory holds, so it can only come from POST /v1/suppliers/from-directory.
A supplier declared for e-Factura that carries a fiscal identifier and no countryCode is given
RO, for the reason written down under PUT /v1/suppliers/{id}/connection-methods.
Answers 200 with the connection.
PUT /v1/suppliers/{id}/connection-methods
Replaces the routes the firm declares for one supplier it already holds. ManageSources.
| Field | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | yes | The client company |
connectionMethods |
array | yes | The routes the firm declares now |
This is the toggle behind the e-Factura tick on the sources panel, and it replaces rather than
adds. POST /v1/suppliers folds what you send into what the row already holds, so it can turn
e-Factura on and never off. This route can do both: send [2] to say the supplier invoices
through ANAF, and send [] or [1] to say it no longer does.
connectionMethods is required. Leaving it out is 400, not an empty list: an omitted field and
an empty array would otherwise mean the same thing, and a caller that forgot the field would untick
e-Factura and be told it worked.
Two things the request cannot change. 1 stays on the row whatever you send, because email
forwarding asks nothing of the supplier and a row without it describes a company nobody can
connect. 4 is left exactly as it is, neither added nor removed, because a connector needs a slug
only the shared directory holds. Sending 0 or 4 is refused with 400, the same as on
POST /v1/suppliers.
A supplier that ends up declared for e-Factura, carries a fiscal identifier, and has no
countryCode yet is given RO. ANAF carries Romanian companies and nobody else, and the country
is what lets an incoming e-Factura invoice find the row: matching is on the country and the fiscal
identifier together, so a supplier typed in as a bare CUI with no country beside it is a row the
invoice walks past. A supplier that already has a country keeps it, and one with no fiscal
identifier is left alone.
Answers 200 with the connection, the same shape GET /v1/suppliers/connections returns, so the
connectionMethods on it are the new set. A supplier that belongs to another client company
answers 404. Sending the routes the row already has changes nothing and writes nothing, so it is
safe to repeat.
A supplier flagged 2 here is, together with every other supplier flagged 2 on the same client,
exactly the "listed suppliers" set that an e-Factura enrollment's listedSuppliers collection scope
files against - see e-Factura and SPV. The enrollment screen can add
to that set on its own: when the client has not already added a supplier by hand, it creates a
minimal one from just a name and a Romanian tax identifier through POST /v1/suppliers. So does a
filed e-Factura invoice: its issuer joins the set, which means a client that collected everything
and later narrows to listedSuppliers keeps receiving from the suppliers it has been receiving
from.
GET /v1/supplier-directory
Every listed supplier, alphabetical. Any signed-in credential. Answers a plain array, not a paged envelope.
| Parameter | Type | Means |
|---|---|---|
search |
string | Optional match against the display name |
| Field | Type | Means |
|---|---|---|
id |
uuid | The directory entry |
slug |
string | Pass this to POST /v1/suppliers/from-directory |
displayName |
string | The company's name |
aliases |
array of string | Other names it invoices under |
countryCode |
string or null | Two letters |
taxIdentifierType |
TaxIdentifierType |
Its identifier kind, when one is published |
taxIdentifierValue |
string or null | Its identifier |
senderAddresses |
array of string | Addresses its invoices come from |
connectionMethods |
array | How invoices from it can arrive |
connectorSlug |
string or null | The portal connector, when there is one |
tutorialContentKey |
string | The key of its setup instructions |
This is a shared catalog, the same for every organization. It is how Syllevo recognizes a supplier on the first invoice rather than the third.
Not every entry has a taxIdentifierValue. Some companies do not publish one that is usable for
matching, and a blank field is honest about that rather than guessed at.
POST /v1/suppliers/{id}/merge
Folds two supplier rows for one company into one. ManageSources. Both rows must belong to the
same client company.
The supplier in the path is the one being removed. Everything hanging off it, invoices, documents, sender addresses, moves to the survivor first.
Name the survivor exactly one of two ways:
| Field | Type | Means |
|---|---|---|
intoSupplierId |
uuid | Another supplier the client already holds |
intoDirectorySlug |
string | A directory entry, connected to the client on the way through |
Sending both is 400. Sending neither is 400.
The intoDirectorySlug form is how a row that was extracted off an invoice becomes the official
directory-backed row. If the client already holds the directory row, it is found rather than
duplicated.
POST /v1/suppliers/44444444-4444-4444-8444-444444444444/merge HTTP/1.1
Host: api.syllevo.com
X-Api-Key: syl_live_examplekeynotreal000000000000
Content-Type: application/json
{ "intoDirectorySlug": "example-utilities" }
Answers 200 with the surviving supplier. Refusals:
| Status | Code | Means |
|---|---|---|
400 |
supplier_merge_same_supplier |
Both halves are the same supplier |
409 |
supplier_merge_profile_mismatch |
The two belong to different client companies |
409 |
supplier_merge_official_supplier |
The row being removed is the directory-backed one. Merge the other way |
409 |
supplier_merge_already_official |
It is already the directory entry it was aimed at |
Merging is not reversible. Read both rows first.
VAT validation
What the European Commission says about a client's foreign suppliers. Read-only, and that is the design rather than a first slice: the verdict is the Commission's, so there is nothing here to dismiss or override. A row changes when somebody corrects the supplier's VAT number and the next pass gets a different answer.
Only EU suppliers appear. Domestic suppliers are not in this list.
GET /v1/vat-validations
One page, worst news first.
| Parameter | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | no | Narrow to one client |
page |
integer | no | Default 1 |
pageSize |
integer | no | Default 25 |
| Field | Type | Means |
|---|---|---|
supplierId |
uuid | The supplier |
profileId |
uuid | The client company |
supplierName |
string | Its name on file |
countryCode |
string | Two letters, upper case, always an EU member state |
vatNumber |
string or null | Normalized, upper case, without the country prefix |
status |
string | invalid, missing, unchecked, or valid |
checkedAt |
date-time, null | When the Commission last answered |
viesName |
string or null | The trading name the Commission holds for that number |
status is a lower-case word rather than a number, unlike every other enum on this API. Two of its
four values are not stored anywhere, so there is no enum for it to be the wire form of. See
Conventions.
checkedAt is never moved by a failed check. A row that says it was checked three weeks ago was
checked three weeks ago.
Show viesName next to the supplier's name. A VAT number can be perfectly valid and belong to
somebody else entirely, and this is the only field on the row that would say so. It is null for the
member states that publish no trader details.
GET /v1/vat-validations/count
The badge, over the clients the caller can see.
| Field | Type | Means |
|---|---|---|
invalid |
integer | Suppliers whose VAT number the Commission says is not registered |
missing |
integer | EU suppliers with no VAT identifier on file at all |
Only the two states somebody has to act on. unchecked is not counted because Syllevo's own pass
clears it within the hour, and valid is not counted because a badge that is never zero is not a
badge.