Sari la conținut
Pagini APISearch and company lookup

Documentație API

Această documentație este disponibilă doar în limba engleză.

Search and company lookup

Two routes that answer questions rather than return resources. One searches everything the caller can see. The other asks ANAF who a Romanian fiscal code belongs to.

Routes

Method Path Needs
GET /v1/search Any credential
GET /v1/registry/company Any credential, no organization

GET /v1/search

One query, seven or eight categories, up to three matches in each. Type "scaffolding rental" and find the invoice, whichever client it belongs to; type half a client name, a colleague, or a supplier and find those too.

Parameter Type Means
q string What was typed. 2 to 200 characters
profileId uuid Optional. Narrows documents, sources, SPV messages, and the firm's own suppliers to one client

Rate limited to 120 requests a minute per organization under the search policy. It is one endpoint fanning out server side rather than one per category, because seven requests per keystroke would refuse five colleagues at once. See Conventions.

The response

Field Type Means
query string The query as it was understood, trimmed
profileId uuid or null The narrowing that was applied
documents category Matches in invoices and documents
profiles category Client companies by name or tax id
suppliers category The firm's suppliers, then the shared directory
sources category Connected sources
spvMessages category ANAF messages
organizations category Organizations the caller belongs to
users category or null Colleagues. Null for anyone who is not an Admin

Every category has the same envelope:

Field Type Means
items array Up to limit matches
limit integer Always 3
totalCount integer How many matched, capped at 1000
totalPages integer totalCount divided by limit
timedOut boolean True when this category ran out of time and answered nothing

timedOut: true means unknown, not empty. Each category runs against its own deadline, 2.5 seconds for documents and 1 second for each of the name lookups, so one slow query never holds the response. Show "still looking" or offer a retry for a category that timed out. Showing "no results" there tells an accountant an invoice is gone when it is not, which is the one thing this product may never do.

totalCount stops counting out loud at 1000. A query matching more says exactly 1000. Nothing is hidden and the returned rows are unaffected; a search matching a thousand documents needs different words.

What matches

Category Matches on
documents Invoice number, supplier, tax id, amounts, line items, senders, file names, text
profiles Client name and tax identifier
suppliers Supplier name, fiscal code, contact address, sender addresses, and directory slug
sources Source tag and provider type
spvMessages ANAF message id and type
organizations Organization name
users Colleague display name

Words are matched together and diacritics are forgiven, so typing schela finds schelă.

The documents category is ranked, not just filtered. An invoice's number, supplier, tax id, and totals weigh the most, then its line items, then how it arrived (sender addresses, file names, account tags), then the text captured from the file. So typing a supplier name puts the invoices from that supplier above a document that merely mentions it in a paragraph.

One row per invoice, not per file. An invoice that arrived as an e-Factura XML and again as the supplier's own PDF is one result, and documentId is the most trusted copy that is still there. A document nothing has been filed from yet is its own row, so something still in extraction or review turns up rather than going quiet until somebody files it.

A document in the trash never matches, even though its captured text is kept for the sixty-day retention window in case it comes back. If the trashed file was the most trusted copy of an invoice, the row opens the best copy that is left. A purge removes the entry keyed on the purged document, and the entry of any invoice the purge leaves with no document at all; deleting an invoice removes its entry too. An invoice another document still evidences keeps its entry, and that entry holds what the purged document said until the next refresh rewrites it.

Line items became searchable on 2026-09-19. Lines are recorded when an invoice is filed, so line items exist only for invoices filed on or after that date. An invoice filed before it is searchable by its header, by how it arrived, and by its text, never by line – a document that has already been processed is not read again. The backfill that walks the older invoices, 500 subjects an hour, fills in their header and their text, not their lines. Two limits: at most 200 lines are kept per invoice, and at most 50,000 characters of line text go into the index. An invoice filed from a source that never sent lines - a scan, a PDF with nothing machine-readable in it - has none, and matches on its header and its text.

The text half only reaches text Syllevo could read: structured e-Factura XML and PDFs that carry a text layer. A scan or a phone photo has no captured text, so it cannot match on its contents. It still matches through its supplier, its invoice number, its amounts, or how it arrived - all of which come from the filed invoice rather than from the file.

Result shapes

documents items:

Field Type Means
documentId uuid The document to open
profileId uuid Which client it belongs to
fileName string or null What it arrived as
snippet string The matched text with context around it
matchedIn integer Which part of the row matched
matchedLine object or null The matched invoice line, when one matched
receivedAt date-time When Syllevo first got it
lastArrivalAt date-time When it last arrived again
invoiceId uuid or null The invoice it evidences, when it has one
invoiceNumber string or null That invoice's number
invoiceIssueDate date, null Its issue date
invoiceTotalAmount number or null Its total
invoiceCurrencyCode string or null Its currency
supplierName string or null Who it is from

The invoice fields are null as a group when nothing has been filed from the document yet.

snippet is the excerpt to show: about 90 characters on each side of the first matched term, or the first 180 characters when no term could be located.

matchedIn says which part of the row the query was found in, so a result can be labeled rather than just listed. It travels as an integer:

Value Means
0 The text captured from the file
1 The header: number, supplier, tax id, currency, amounts
2 An invoice line
3 How it arrived: sender addresses, file names, source tags

Some matches are found by the index and cannot be found again in the plain text - a tax id stored without its separators, an amount written with the other decimal separator, a sender address split into its parts. Those rows report the part the excerpt was taken from rather than a fifth "not sure" value, so matchedIn is always one of the four above. The excerpt then comes from the first of 1, 3, 0, 2 that has anything in it, in that order, because an unlocatable match almost always came from the header or the arrival context.

matchedLine is set only when a specific line was located, so it can be null even when matchedIn is 2. That happens on the fallback above: the row is labeled as a line because the excerpt came from the line text, but no single line matched the query, and naming one anyway would tell an accountant "this is the line you were looking for" about a line nobody asked for. Draw the snippet when matchedLine is null.

When it is set:

Field Type Means
description string What was bought, as the invoice printed it
quantity number or null How much of it
unit string or null The unit as printed - buc, H87, KGM
unitPrice number or null Price per unit
netAmount number or null The line amount before VAT
lineTotal number or null The line total including tax
currencyCode string or null The invoice's currency, for formatting

Draw netAmount as the total of the quantity x unitPrice arithmetic, because that is the amount the multiplication equals. Syllevo's own search palette does, and falls back to lineTotal when the document printed no net amount. An e-Factura line carries a net amount and usually no gross one, so a client that reads lineTotal alone shows no arithmetic for most Romanian invoices.

Amounts travel raw and are formatted per locale by the client, the same rule as everywhere else on this API. snippet still carries the line's description, so a client that draws only the excerpt loses nothing.

suppliers items carry displayName, countryCode, and exactly one id:

Shape Ids Also
The firm's own supplierId and profileId taxIdentifier
Shared directory supplierDirectoryEntryId and slug -

The firm's own rows come first and the directory fills what is left of the three, because somebody typing a supplier name while looking at a client usually means that client's supplier, not a public entry they might one day connect. totalCount is both halves added together.

Supplier email and sender addresses are matched and never returned. Somebody who knows a supplier only as "the one that mails from billing@" has to be able to find it, and a search that printed the address back would confirm it to anybody who can type two characters.

The remaining categories are small and name-shaped: profiles carry profileId, name, taxIdentifier, countryCode, and avatar links; sources carry accountId, profileId, providerType, and tag; spvMessages carry messageId, profileId, anafMessageId, type, and anafCreatedAt; organizations carry organizationId, name, and avatar links; users carry membershipId, displayName, role, and avatar links.

Edge cases that are not errors

  • A query under 2 characters answers 200 with every category empty. The box fires while somebody is still typing, and an error for each keystroke would be noise.
  • A caller with no grants gets empty categories, the same way the invoice list gets an empty page.
  • users is absent for anyone below Admin, rather than the whole request being refused. A non-admin still gets their documents and their clients.
  • profileId can only ever narrow. Naming a client the caller cannot see removes every row rather than adding one.

GET /v1/registry/company

Looks one Romanian fiscal code up in ANAF's public registers, as of today. This is the route an onboarding form calls when somebody types a CUI.

Parameter Type Required Means
cui string yes The fiscal code. Up to 32 characters, RO prefix and spaces tolerated

This parameter was spelled Cui before 2026-09-11. It is cui now, in the contract and in any client generated from it. Query string keys are matched without regard to case, so a caller still sending Cui= keeps working. See Changelog.

This route takes no X-Organization-Id. It is reached while an accountant is adding their first client company, often before they have an organization at all. See Authentication.

Field Type Means
found boolean Whether ANAF knows this code
cui string The code, as it was asked
legalName string or null The registered name
address string or null The registered address
tradeRegisterNumber string or null The trade register number
vatRegistered boolean Whether it is registered for VAT
eFacturaEnrolled boolean Whether it is in the e-Factura register
eFacturaEnrolledSince date, null When it joined
{
  "found": true,
  "cui": "RO00000000",
  "legalName": "Example Contabil SRL",
  "address": "Str. Exemplu 1, Bucuresti",
  "tradeRegisterNumber": "J00/0000/2020",
  "vatRegistered": true,
  "eFacturaEnrolled": true,
  "eFacturaEnrolledSince": "2024-07-01"
}

Rate limited to 20 requests a minute under the registry policy. That is not protecting ANAF, which has its own one-request-a-second limit held process-wide inside Syllevo. It is protecting the queue in front of that limiter from one caller looping over a client list while everybody else's form waits behind them. Look codes up as people type them, not in a batch.

Two answers that must never be confused

What happened Answer
ANAF does not know this code 200 with found: false
ANAF could not be reached or understood 503 provider_unreachable

This route never answers 404. A code nobody recognizes is a 200 the form can render, and a 404 would read as "that route is gone".

Treat the 503 as "ask again", not as "this company does not exist". ANAF is down for hours at a time, and its public host sits behind a filter that sometimes answers 200 with an HTML rejection page. Telling an accountant their client is not in e-Factura when Syllevo simply could not ask would have it stop expecting invoices that are on their way.

A missing or over-long cui answers 400 with a field error on cui.