The HTTP connector is a machine-to-machine API. Before your integration code can call it, a human admin at the customer's company must complete a one-time setup in our app to obtain three credentials.
The admin signs up at our app, verifies their email, and registers their business (uploads V-SDC certificate, validates PAC, enters TaxCore UID + licence key). This produces a unique BUSINESS_ID UUID tied to the business's fiscalisation credentials.
In the app: Integrations → Generic HTTP → Issue new key. The key carries no location: where an invoice signs is decided per invoice by the store code on it, not by the credential. One key serves the whole business, however many outlets it has. A one-shot reveal modal then displays three values, each with a copy button:
https://api.vsmsconnect.com/api/v17f3a1e2c-...a3f2b1c8d9e7f6a4b2c1d8e9f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8The API key is shown once. The customer's admin must copy it and hand it (plus the Backend URL and Business ID) to their integration team — usually via their internal password manager. If the key is lost, the admin revokes it and issues a new one. Backend URL and Business ID remain visible in the app's API Keys list.
Set three environment variables (or config equivalents) in your code:
VSMS_CONNECT_BACKEND_URL = "https://api.vsmsconnect.com/api/v1" VSMS_CONNECT_BUSINESS_ID = "7f3a1e2c-..." VSMS_CONNECT_API_KEY = "a3f2b1c8d9e7f6a4..."
Every request goes to:
POST {VSMS_CONNECT_BACKEND_URL}/businesses/{VSMS_CONNECT_BUSINESS_ID}/fiscalisewith headers:
Authorization: ApiKey {VSMS_CONNECT_API_KEY}
Idempotency-Key: <optional — omit it and the server derives one from invoiceNumber>
Content-Type: application/jsonUnless you send raw taxLabels, POST your tax table once to {VSMS_CONNECT_BACKEND_URL}/businesses/{VSMS_CONNECT_BUSINESS_ID}/tax-rates (same ApiKey auth) so a business admin can map every taxCode to a V-SDC label before you send live invoices. Until a code is mapped, invoices using it are accepted but blocked with MISSING_TAX_MAPPING. There are no pre-seeded defaults — a brand-new business cannot fiscalise anything until you declare at least one code and an admin maps it. See Tax codes & labels below for the full model.
POST your store list once to {VSMS_CONNECT_BACKEND_URL}/businesses/{VSMS_CONNECT_BUSINESS_ID}/stores (same ApiKey auth), then send storeCode — your own identifier — on every invoice. You never store a VSMS Connect GUID against your shops. GET the same path to read back what each code currently resolves to.
Do this even if the business has a single shop. The rule is not about how many outlets there are — it is that any store code you send has to be mapped before it can sign. A single-location business that sends a code you never declared gets the same block as a chain would. Declaring one store and having it mapped is a minute of setup, and it means the integration behaves identically on the day the customer opens their second shop.
An admin then maps each declared code to one of the business’s Locations in the app. That mapping is what decides which V-SDC certificate signs the store’s invoices.
An invoice whose store code cannot be resolved is accepted and blocked, never signed somewhere else. It returns 201 with eligibleForFiscalisation: false and a reason in fiscalisationBlockReasons:
| reason | meaning | who fixes it |
|---|---|---|
HTTP_STORE_NOT_MAPPED | the code is not mapped to a Location yet (never declared, or declared and awaiting the admin) | the business admin, on the Stores tab |
HTTP_STORE_LOCATION_NO_CERTIFICATE | mapped, but that Location holds no active certificate | the business admin, on the Locations screen |
HTTP_STORE_CODE_REQUIRED | no store code was sent at all, and more than one Location holds a certificate — there is no way to tell which should sign | you — start sending storeCode |
Blocking rather than rejecting is deliberate: a 4xx would lose a real sale over a configuration gap, and an unknown code would never become visible to the admin. The invoice is kept, the code is proposed for review, and the admin’s Re-sync blocked invoices releases it once the mapping exists. Nothing is silently signed under a certificate the sale does not belong to.
The one case that needs no setup at all: a business with a single certified Location, where you send no selector on any invoice. Every sale then signs at that Location. This is a genuine zero-config path, but it is all-or-nothing — send a store code and it must be mapped, exactly as above. It also stops being available the moment the customer certifies a second Location, at which point invoices with no code block with HTTP_STORE_CODE_REQUIRED until you start sending one. Declaring your stores up front avoids that day entirely.
The locationId escape hatch. A caller that already holds our internal GUID may send locationId instead (must belong to the business, else 422 LOCATION_NOT_FOUND). Send exactly one of the two — 422 LOCATION_SELECTOR_EXCLUSIVE if you send both. New integrations should use storeCode.
Either selector affects a fresh sale only: refunds and copies inherit the original sale’s location and certificate automatically.
If your software integrates with multiple of our customers, each customer's admin completes Step 1 + Step 2 independently. You end up with one Business ID and one API key per customer — not per outlet, since the key carries no location. Each customer’s outlets are told apart by the storeCode on each invoice, so your per-customer configuration stays two values however many shops they run. The Backend URL is the same for every customer.
Each line item carries exactly one of taxCode or taxLabel — and either one takes a single value or an array.
An item can bear more than one tax. A label identifies one rate inside one tax category, and an item may be liable for several categories at once — VAT plus a levy, say. Pass an array when it is: "taxCode": ["VAT15", "ECAL"], or "taxLabel": ["A", "D"] if you are using raw labels. At most one tax per category; a repeated entry is rejected (TAX_CODES_DUPLICATE), and there is no cap on how many categories an item may carry.
Note this is the same field, not a separate plural one: "VAT15" and ["VAT15"] mean exactly the same thing. Existing integrations need no change, and adding a second tax to a line means putting the value you already send in brackets.
Every amount is still computed by V-SDC from the labels and the item total — which is the only place it can be right, because a tax-on-total rate is charged on the item total including the other taxes on that line, and an amount-per-quantity tax makes the remaining taxes compute on the remainder. Sending several labels attaches them; it never asks you to do that arithmetic.
taxCode is a semantic code from your system — an open vocabulary (any non-empty string up to 100 chars, e.g. VAT15, VAT0, EXCISE). VSMS Connect cannot pull your tax catalogue, so you declare your codes via POST /tax-rates and a business admin maps each one to a V-SDC label in the app before the first invoice. A code with no confirmed mapping does not fail the request — the invoice is accepted but blocked with MISSING_TAX_MAPPING until an admin maps it (an unmapped code also surfaces automatically in the admin’s tax-mappings panel). No codes exist by default: taxCode is your own vocabulary, and every code you use has to be declared (or discovered from an invoice) and then mapped by an admin before it can sign.
taxLabel is the escape hatch: send a raw V-SDC label directly and bypass mapping entirely.
The valid labels are defined by TaxCore, not by VSMS Connect — we do not publish the list here. The set and its meanings belong to the tax authority and can be changed by them at any time, so any copy printed in these docs would eventually be wrong in a way you could not detect. VSMS Connect syncs the labels the business’s V-SDC is actually configured with; the authoritative list for a given business is the one offered in the app’s tax-mapping panel, where an admin maps each of your codes to a label. Ask the business admin, or refer to the current TaxCore documentation.
This is also why taxCode is the recommended path: you send your own vocabulary and the admin maps it, so a label change is their configuration problem rather than a release of your software.
A label identifies one specific tax rate — e.g. the 9% rate of the VAT category. Each label belongs to exactly one tax category (VAT, ECAL, PBL…) and never reappears under another, and each category declares how its rates are applied: on the net price, on the total with other taxes included, or as a fixed amount per unit of quantity.
Rates arrive as a revision: TaxCore publishes a tax group with an id and an effective date, so both the rates and the label set can change on a date set by the authority. That is why nothing is hard-coded here — VSMS Connect syncs the group the business’s V-SDC currently reports.
V-SDC computes every tax amount itself, from the labels and the item total. The taxRatePercent and tax amounts you send are used for reconciliation and display on our side; they are never what the fiscal receipt is signed with, and they cannot override the authority’s figures. If your numbers disagree with the SDC’s, the SDC is right by definition.
One endpoint, POST /fiscalise, handles every fiscalisation case via two body fields: invoiceType (NORMAL / COPY / PROFORMA / ADVANCE) and transactionType (SALE / REFUND), plus an optional training: true flag that overrides invoiceType to TRAINING. The other routes are operational / configuration helpers:
POST /tax-rates — declare your tax table so an admin can map each taxCode to a V-SDC label before you send invoices (see Tax codes & labels above).POST /stores — declare your own store codes so an admin can map each one to the Location whose certificate signs it. Any code you send must be mapped first, whatever the number of outlets.GET /stores — read back what each declared code currently resolves to.GET /fiscalise/:invoiceId — idempotent status retrieval after a 202 from POST.POST /fiscalise/:invoiceId/trigger — explicit dispatch for a PROFORMA imported via the 201 path.POST /fiscalise/cancel — submit a V-SDC cancellation document, targeting the payment by fiscalInvoiceNumber or by your own invoiceNumber./api/v1/businesses/:businessId/fiscalise
auth: apikey (scope=http)Main ingestion endpoint. Persists the invoice + dispatches it to V-SDC and sync-waits up to ~10 s for the fiscal response. Returns 200 with `fiscalInvoiceNumber` + QR/URL/hash when the consumer reaches a terminal state inside the window; 202 with a `statusUrl` when the timeout elapses with payments still in-flight; 201 when the invoice was imported but not dispatched — with a `triggerUrl` for PROFORMA bodies (call it to fiscalise the quote), or with a `statusUrl` and no `triggerUrl` for a NORMAL/ADVANCE sale whose connector auto-fiscalise is off (an admin fiscalises it from the app). Every `paymentResults[]` entry carries `eligibleForFiscalisation` + `fiscalisationBlockReasons` so an imported-awaiting-fiscalisation invoice is distinguishable from a blocked one.
handlers: FiscaliseController.fiscalise
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID (must match the API key's BusinessId). |
Authorization headerstring | yes | — | `ApiKey <raw key>` — key must be scoped to `ConnectorType.Http`. |
Idempotency-Key headerstring | no | — | Stable UUID per request. A replay within ~5 min returns the cached response without re-running the handler. |
sync_timeout_ms querynumber | no | 10000 | Sync-wait window in milliseconds. Capped at 30000. |
invoiceNumber bodystring | yes | — | Caller's invoice number. Printed on the V-SDC receipt AND used as the dedup key — `SHA-256(businessId:http:invoiceNumber)` becomes the persisted `IdempotencyKey`. Re-sending the same value returns `409 INVOICE_DUPLICATE`. |
invoiceType body'NORMAL' | 'COPY' | 'PROFORMA' | 'ADVANCE' | yes | — | Case selection. TRAINING is a separate top-level flag — not an invoiceType value. |
transactionType body'SALE' | 'REFUND' | yes | — | Sale or refund. |
training bodyboolean | no | — | When `true`, stamps `invoiceType = TRAINING` regardless of the caller-supplied value. Mutually exclusive with `invoiceType: 'COPY'` (422 `TRAINING_COPY_NOT_SUPPORTED`). |
source body{ referencedFiscalNumber: string; referencedFiscalTimestamp: string } | no | — | COPY only (forbidden otherwise). OPTIONAL since TAXCORE-639: carries the source payment's V-SDC fiscal number + timestamp — explicit and authoritative. Omit it to resolve the copy's source from your reused `invoiceNumber` instead. With `source` omitted the resolution splits on the COPY's own `transactionType`: `SALE` resolves the original sale (advance/split source named by `sourceExternalPaymentId`); `REFUND` resolves the refund from the shared invoiceNumber family — so a refund copy needs no SDC number either (several partial refunds → `sourceExternalPaymentId`). Supplying both `source` and `sourceExternalPaymentId` is rejected. |
referentDocumentNumber bodystring | no | — | OPTIONAL on a REFUND. The source invoice is resolved from your own `invoiceNumber`; this only selects WHICH payment under it is refunded, and the server derives that when the source has a single fiscalised payment. Supply it to pin an exact V-SDC receipt (authoritative when present) — you never have to store the SDC fiscal number to issue a refund. |
referentDocumentDT bodystring | no | — | ISO-8601 timestamp of the referent document. Pairs with `referentDocumentNumber`; the server fills it from the resolved source payment's `fiscalTimestamp` when omitted. |
sourceExternalPaymentId bodystring | no | — | REFUND, or COPY when `source` is omitted. YOUR OWN identifier for the source payment (the `externalPaymentId` you sent on it at sale time). Needed only when the source invoice has more than one fiscalised payment (advance/split) — the server answers 422 naming the candidate ids in that case. On COPY it is mutually exclusive with `source`. Distinct from `payments[].externalPaymentId`, which describes the refund's own tender rows. |
reference bodystring | no | — | PROFORMA → NORMAL conversion key. The fiscalisation module's `linkInvoiceToQuote` stamps `ConvertedFromQuoteId` and carries the proforma's SDC referent through automatically. |
locationId bodyuuid | no | — | Optional per-request location selector, using our internal id. On a fresh SALE it picks which location's default certificate signs the invoice; the value must belong to the business (else 422 LOCATION_NOT_FOUND). Prefer `storeCode` — this field is the escape hatch for callers that already hold the GUID, and new integrations should not use it. Exactly one of `storeCode` / `locationId` (else 422 LOCATION_SELECTOR_EXCLUSIVE). Omitting both is the zero-config path for a single-location business; once more than one location holds a certificate, omitting both blocks the invoice with HTTP_STORE_CODE_REQUIRED rather than guessing. Forbidden on COPY (a copy inherits its source's location) and ignored on REFUND / advance-append. |
storeCode bodystring | no | — | The recommended location selector: YOUR OWN store code (max 100 chars) — declare your codes via POST /businesses/:businessId/stores, then send the code here instead of our GUID. It resolves to the business location an admin has MAPPED the code to, and that location's certificate signs the sale. Routing FAILS CLOSED: an unknown, unmapped, or certificate-less store does NOT fail the call and is NEVER signed at another location — the invoice is accepted (201) and blocked with HTTP_STORE_NOT_MAPPED or HTTP_STORE_LOCATION_NO_CERTIFICATE, the code is proposed for the admin to map, and their re-sync releases it in place. So a new store is never a lost sale and never a mis-signed one. Exactly one of `storeCode` / `locationId`. Forbidden on COPY, ignored on REFUND / advance-append (same rules as `locationId`). |
invoiceDate bodynumber | string | yes | — | Epoch ms or ISO-8601. Required for non-COPY. |
currencyCode bodystring | yes | — | ISO-4217 three-letter currency code. Required for non-COPY. |
cashierId bodystring | yes | — | Cashier / operator id sent to V-SDC as the `cashier` field — printed on every fiscal receipt as the 'Cashier:' line. Required everywhere (Vanuatu V-SDC mandate); whitespace-only values are rejected with `VALIDATION_ERROR`. |
buyer body{ tin?: string; name?: string; costCentreId?: string; email?: string } | null | no | — | Optional. Presence triggers the 'with buyer' V-SDC wire body. V-SDC carries TIN + name + costCentreId on the wire receipt; `email` is stored locally and used by the auto-email pipeline to send the customer a copy of the fiscalised receipt when the per-connector auto-email toggle is enabled. |
lineItems bodyArray<{ description, quantity, unitPrice, (taxCode | taxLabel), taxRatePercent, lineSubtotal, lineTaxAmount, lineTotal, gtin?, sortOrder?, itemAdditionalFields? }> | yes | — | Required non-empty for non-COPY. Each line carries EXACTLY ONE of `taxCode` or `taxLabel`. `taxCode` is a semantic code resolved to a V-SDC label via the business's tax mappings — an OPEN vocabulary (any non-empty string, max 100 chars). There are NO pre-seeded codes: every code, including `VAT15` / `VAT0`, must be declared and mapped by an admin, and any unmapped code is accepted but blocks the invoice with `MISSING_TAX_MAPPING` until they map it (the code then surfaces in the admin's tax-mappings panel — declare codes up front via `POST /tax-rates` to avoid the first-invoice block). `taxLabel` is a raw V-SDC label that bypasses mapping — the valid set is defined by TaxCore and can change, so it is not enumerated here; see the tax-mapping panel in the app or the current TaxCore documentation. For an item bearing SEVERAL taxes, pass an ARRAY to the same field — `taxCode: ['VAT15','ECAL']` or `taxLabel: ['A','D']` — one entry per tax category, no duplicates, and no limit on how many. A bare string is equivalent to a one-element array, so existing callers are unaffected. If ANY code in the array is unmapped the whole line is blocked with MISSING_TAX_MAPPING rather than signed with the mapped subset: a partial label set would produce a receipt that looks successful and under-reports tax. For an item bearing SEVERAL taxes, pass an ARRAY to the same field — `taxCode: ['VAT15','ECAL']` or `taxLabel: ['A','D']` — one entry per tax category, no duplicates, and no limit on how many. A bare string is equivalent to a one-element array, so existing callers are unaffected. If ANY code in the array is unmapped the whole line is blocked with MISSING_TAX_MAPPING rather than signed with the mapped subset: a partial label set would produce a receipt that looks successful and under-reports tax. |
payments bodyArray<{ amount, paymentType, paymentDate, externalPaymentId?, tenders? }> | yes | — | Required non-empty for non-COPY. For ADVANCE deposit chains: send ONE invoice with multiple payments — the consumer auto-chains ADVANCE → ADVANCE → … → NORMAL. Each payment may carry a `tenders[]` breakdown for split-tender. |
subtotalAmount bodynumber | yes | — | Sum of `lineItems[].lineSubtotal` (±0.01 reconciliation). |
taxAmount bodynumber | yes | — | Sum of `lineItems[].lineTaxAmount` (±0.01 reconciliation). |
totalAmount bodynumber | yes | — | Sum of `lineItems[].lineTotal` (±0.01 reconciliation). Must also equal sum of `payments[].amount`. |
invoiceAdditionalFields bodyRecord<string, string> | no | — | Optional bag forwarded verbatim to V-SDC as `invoiceAdditionalFields`. |
paymentAdditionalFields bodyRecord<string, string> | no | — | Optional bag forwarded verbatim to V-SDC inside the payment array. |
APIResponseDataObject<FiscaliseSuccessResponse>APIResponseDataObject<FiscaliseAcceptedResponse>APIResponseDataObject<FiscaliseSuccessResponse>APIErrorAPIErrorAPIError/api/v1/businesses/:businessId/tax-rates
auth: apikey (scope=http)Declare the caller's tax table — the push-connector equivalent of "list all tax rates". VSMS Connect cannot pull an integrator's tax catalogue, so the caller declares each code it will use and a business admin maps every one to a V-SDC label before the first invoice arrives. Each not-yet-mapped code is recorded as a PROPOSAL for admin confirmation; nothing is ever auto-confirmed, and a confirmed mapping's V-SDC label is never rewritten by a caller. Re-declaring a confirmed code whose rate has drifted flags that mapping for admin re-review (its invoices block until re-mapped); a name-only change is a non-blocking metadata refresh. Not idempotency-keyed — a re-declaration is always processed. Modelled on the on-premise agents' seed-from-agent flow.
handlers: HttpTaxRatesController.declare
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID (must match the API key's BusinessId). |
Authorization headerstring | yes | — | `ApiKey <raw key>` — key must be scoped to `ConnectorType.Http` (the same credential POST /fiscalise uses). |
taxRates bodyArray<{ code: string; name?: string | null; rate?: number | null }> | yes | — | The caller's tax table. `code` is the semantic tax code sent on invoice line items (non-empty, max 100 chars); `name` (max 500) and `rate` (percent) are optional and may be null — send what you know. Each declared code with no confirmed mapping becomes a proposal for the admin to map. |
APIResponseDataObject<{ proposed: Array<{ code; name; rate }>; driftDetected: number; nameRefreshed: number; alreadyMapped: number }>APIError/api/v1/businesses/:businessId/stores
auth: apikey (scope=http)Declare your own store codes, so you never have to store a VSMS Connect location GUID. Each declared code is recorded against the business for review; an admin then MAPS it to one of their own Locations, and that location's certificate signs the store's sales. Idempotent — re-declaring a code never overwrites what the admin has already set, so it is safe to send your full store list on every deploy. Send this BEFORE your first invoice: until a code is mapped, invoices carrying it are accepted but blocked with HTTP_STORE_NOT_MAPPED rather than signed at another location. Not idempotency-keyed: a re-declaration is always processed.
handlers: HttpLocationsController.declare
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID (must match the API key's BusinessId). |
Authorization headerstring | yes | — | `ApiKey <raw key>` — key must be scoped to `ConnectorType.Http` (the same credential POST /fiscalise uses). |
stores bodyArray<{ storeCode: string; name?: string | null; street?: string | null; city?: string | null }> | yes | — | Your stores. `storeCode` is YOUR identifier for the store (non-empty, max 100 chars) — the value you will send as `storeCode` on POST /fiscalise. `name` (max 255) is the label the admin sees when reviewing; omit it and the code is used as the label. `street` / `city` are optional free text. |
APIResponseDataObject<{ declared: number; proposed: string[]; alreadyKnown: string[] }>APIError/api/v1/businesses/:businessId/stores
auth: apikey (scope=http)Read back what your declared store codes currently resolve to, so you can confirm the mapping before relying on it. Returns only stores declared through this connector — never the business's other locations, which you have no code for. Check this before going live: a store that is not yet `mapped` does NOT route, and every invoice carrying its code is accepted and BLOCKED rather than signed at another location.
handlers: HttpLocationsController.list
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID (must match the API key's BusinessId). |
Authorization headerstring | yes | — | `ApiKey <raw key>` — key must be scoped to `ConnectorType.Http`. |
APIResponseDataObject<{ stores: Array<{ storeCode: string; name: string | null; status: 'mapped' | 'proposed' | 'rejected'; locationId: string }> }>/api/v1/businesses/:businessId/fiscalise/:invoiceId
auth: apikey (scope=http)Retrieve one invoice by its VSMS Connect `invoiceId` — the connector's only retrieval endpoint. Lookup is by the server-issued `invoiceId` GUID (echoed in every POST /fiscalise response), NOT the caller's `invoiceNumber` and NOT the SDC `fiscalInvoiceNumber`; there is no lookup-by-number and no list/search endpoint, so the integrator must persist the `invoiceId` it gets back. Idempotent — returns the same `FiscaliseSuccessResponse` shape as `POST /fiscalise` (per-payment `status`, `fiscalInvoiceNumber` null until signed, plus `eligibleForFiscalisation`/`fiscalisationBlockReasons`), so a caller polling after a 202 gets a drop-in replacement once the consumer reaches a terminal state.
handlers: FiscaliseController.getStatus
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID. |
invoiceId pathuuid | yes | — | The server-issued invoiceId GUID returned by a prior POST /fiscalise — not the caller's invoiceNumber and not the SDC fiscalInvoiceNumber. |
Authorization headerstring | yes | — | `ApiKey <raw key>` scoped to `ConnectorType.Http`. |
APIResponseDataObject<FiscaliseSuccessResponse>APIError/api/v1/businesses/:businessId/fiscalise/:invoiceId/trigger
auth: apikey (scope=http)Explicit dispatch for a PROFORMA imported via POST /fiscalise's 201 path. Delegates to the existing `triggerFiscalisationByInvoiceIds` primitive — non-PROFORMA invoices are rejected with 422 INVOICE_NOT_QUOTE.
handlers: FiscaliseController.trigger
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID. |
invoiceId pathuuid | yes | — | PROFORMA invoice id. |
Authorization headerstring | yes | — | `ApiKey <raw key>` scoped to `ConnectorType.Http`. |
Idempotency-Key headerstring | no | — | Stable UUID per request. |
APIResponseDataObject<{ invoiceId; outcome; jobId; statusUrl }>APIError/api/v1/businesses/:businessId/fiscalise/cancel
auth: apikey (scope=http)Submit a V-SDC cancellation document for a previously-fiscalised payment. Name the target by `fiscalInvoiceNumber` (the SDC number) OR by your own `invoiceNumber` (+ optional `transactionType`/`externalPaymentId`), resolved against that number's sale/refund/copy family (TAXCORE-639). Because a cancel signs a reversing document the invoiceNumber path is fail-closed — an ambiguous target returns 422 naming the candidates, never guessed. Delegates to the per-payment `cancelPayment` primitive (sibling InvoicePayments row with `CancelsPaymentId`) and waits for the cancellation receipt to be signed before responding — so the caller gets back the full `fiscalJournal` + QR + signed hash in one round-trip and can print without polling. Falls through to 202 + `statusUrl` if the sync-wait window elapses.
handlers: FiscaliseController.cancel
| name | required | default | description |
|---|---|---|---|
businessId pathuuid | yes | — | Target business UUID. |
Authorization headerstring | yes | — | `ApiKey <raw key>` scoped to `ConnectorType.Http`. |
Idempotency-Key headerstring | no | — | Stable UUID per request. |
fiscalInvoiceNumber bodystring | no | — | SDC `fiscalInvoiceNumber` returned by a prior 200 response. Mutually exclusive with the invoiceNumber selectors; one of the two targets is required. |
invoiceNumber bodystring | no | — | Your own invoiceNumber (TAXCORE-639) — resolves the cancel target within that number's sale/refund/copy family. Fail-closed on ambiguity (422). |
transactionType body'SALE' | 'REFUND' | no | — | Disambiguator for the invoiceNumber path — pick the sale or the refund under that number. |
externalPaymentId bodystring | no | — | Disambiguator for the invoiceNumber path — your own id for the exact payment to cancel. |
APIResponseDataObject<FiscaliseCancelResponse>APIResponseDataObject<FiscaliseCancelResponse>APIErrorAPIErrorAPIError