curl --request POST \
--url https://api.novatrade24.com/v1/partners/{sellerId}/buyers \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"vatNumber": "DE123456789",
"companyName": "<string>",
"address": {
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"contacts": [
{
"firstName": "<string>",
"lastName": "<string>",
"email": "[email protected]",
"phone": "<string>"
}
]
}
'import requests
url = "https://api.novatrade24.com/v1/partners/{sellerId}/buyers"
payload = {
"vatNumber": "DE123456789",
"companyName": "<string>",
"address": {
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"contacts": [
{
"firstName": "<string>",
"lastName": "<string>",
"email": "[email protected]",
"phone": "<string>"
}
]
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
vatNumber: 'DE123456789',
companyName: '<string>',
address: {street: '<string>', postCode: '<string>', city: '<string>', country: 'DE'},
contacts: [
{
firstName: '<string>',
lastName: '<string>',
email: '[email protected]',
phone: '<string>'
}
]
})
};
fetch('https://api.novatrade24.com/v1/partners/{sellerId}/buyers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.novatrade24.com/v1/partners/{sellerId}/buyers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'vatNumber' => 'DE123456789',
'companyName' => '<string>',
'address' => [
'street' => '<string>',
'postCode' => '<string>',
'city' => '<string>',
'country' => 'DE'
],
'contacts' => [
[
'firstName' => '<string>',
'lastName' => '<string>',
'email' => '[email protected]',
'phone' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.novatrade24.com/v1/partners/{sellerId}/buyers"
payload := strings.NewReader("{\n \"vatNumber\": \"DE123456789\",\n \"companyName\": \"<string>\",\n \"address\": {\n \"street\": \"<string>\",\n \"postCode\": \"<string>\",\n \"city\": \"<string>\",\n \"country\": \"DE\"\n },\n \"contacts\": [\n {\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"[email protected]\",\n \"phone\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.novatrade24.com/v1/partners/{sellerId}/buyers")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"vatNumber\": \"DE123456789\",\n \"companyName\": \"<string>\",\n \"address\": {\n \"street\": \"<string>\",\n \"postCode\": \"<string>\",\n \"city\": \"<string>\",\n \"country\": \"DE\"\n },\n \"contacts\": [\n {\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"[email protected]\",\n \"phone\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.novatrade24.com/v1/partners/{sellerId}/buyers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"vatNumber\": \"DE123456789\",\n \"companyName\": \"<string>\",\n \"address\": {\n \"street\": \"<string>\",\n \"postCode\": \"<string>\",\n \"city\": \"<string>\",\n \"country\": \"DE\"\n },\n \"contacts\": [\n {\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"[email protected]\",\n \"phone\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"companyNumber": "NT-4K7M-9XQ2",
"type": "COMPANY",
"companyName": "<string>",
"address": {
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"kycMode": "MARKETPLACE_LED",
"status": "ACTIVE",
"version": 123,
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"vatNumber": "<string>",
"inactiveSince": "2023-11-07T05:31:56Z",
"notApplied": [
"companyName"
]
}{
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"companyNumber": "NT-4K7M-9XQ2",
"type": "COMPANY",
"companyName": "<string>",
"address": {
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"kycMode": "MARKETPLACE_LED",
"status": "ACTIVE",
"version": 123,
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"vatNumber": "<string>",
"inactiveSince": "2023-11-07T05:31:56Z",
"notApplied": [
"companyName"
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>",
"rejectedValue": "<unknown>"
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}Upsert a buyer (create or return existing by VAT).
Upserts a buyer by (sellerId, vatNumber). A second call returns 200
with the existing buyer at an unchanged version, whatever else the
body says: the resolve arm writes nothing to the buyer. Use PATCH
under If-Match to change a field on purpose.
Where your body says something other than what the record says, the
200 names those fields in notApplied. Read it: a call that meant to
correct a company name and got notApplied: ["companyName"] did not
correct anything.
Drift is reported rather than refused because the record is shared.
Another partner holding this buyer’s KYC result may correct its company
name or registered address — that is the network effect a single KYC
record exists for — and your unchanged body would otherwise be refused
for good through no act of yours. notApplied describes your request,
not the resource, so two callers reading the same buyer still see the
same ETag.
POST /orders compares differently, and deliberately: an order belongs
to the partner that recorded it, nobody else can move it, so a drifted
externalOrderId replay there still answers 409.
contacts resolves per person on the email address, exactly as
POST /contacts does — see the schema. Its entries report into the same
notApplied list, at contacts[1].firstName and the two beside it, so
one field answers “what did I send that this record does not say” for
the whole body.
Requires Idempotency-Key header for network-retry safety.
kycMode is required. MARKETPLACE_LED is rejected with 403: Mode A
is not available yet, so allowMarketplaceLedKyc is false for every
organization and every buyer is NT24_LED.
The (sellerId, vatNumber) identity applies to COMPANY buyers, and
this operation creates only those. Natural-person buyers are onboarded
through the prospect / Click2Comply flow, not through this API; once
onboarded they are readable here like any other buyer.
curl --request POST \
--url https://api.novatrade24.com/v1/partners/{sellerId}/buyers \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"vatNumber": "DE123456789",
"companyName": "<string>",
"address": {
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"contacts": [
{
"firstName": "<string>",
"lastName": "<string>",
"email": "[email protected]",
"phone": "<string>"
}
]
}
'import requests
url = "https://api.novatrade24.com/v1/partners/{sellerId}/buyers"
payload = {
"vatNumber": "DE123456789",
"companyName": "<string>",
"address": {
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"contacts": [
{
"firstName": "<string>",
"lastName": "<string>",
"email": "[email protected]",
"phone": "<string>"
}
]
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
vatNumber: 'DE123456789',
companyName: '<string>',
address: {street: '<string>', postCode: '<string>', city: '<string>', country: 'DE'},
contacts: [
{
firstName: '<string>',
lastName: '<string>',
email: '[email protected]',
phone: '<string>'
}
]
})
};
fetch('https://api.novatrade24.com/v1/partners/{sellerId}/buyers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.novatrade24.com/v1/partners/{sellerId}/buyers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'vatNumber' => 'DE123456789',
'companyName' => '<string>',
'address' => [
'street' => '<string>',
'postCode' => '<string>',
'city' => '<string>',
'country' => 'DE'
],
'contacts' => [
[
'firstName' => '<string>',
'lastName' => '<string>',
'email' => '[email protected]',
'phone' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.novatrade24.com/v1/partners/{sellerId}/buyers"
payload := strings.NewReader("{\n \"vatNumber\": \"DE123456789\",\n \"companyName\": \"<string>\",\n \"address\": {\n \"street\": \"<string>\",\n \"postCode\": \"<string>\",\n \"city\": \"<string>\",\n \"country\": \"DE\"\n },\n \"contacts\": [\n {\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"[email protected]\",\n \"phone\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.novatrade24.com/v1/partners/{sellerId}/buyers")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"vatNumber\": \"DE123456789\",\n \"companyName\": \"<string>\",\n \"address\": {\n \"street\": \"<string>\",\n \"postCode\": \"<string>\",\n \"city\": \"<string>\",\n \"country\": \"DE\"\n },\n \"contacts\": [\n {\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"[email protected]\",\n \"phone\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.novatrade24.com/v1/partners/{sellerId}/buyers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"vatNumber\": \"DE123456789\",\n \"companyName\": \"<string>\",\n \"address\": {\n \"street\": \"<string>\",\n \"postCode\": \"<string>\",\n \"city\": \"<string>\",\n \"country\": \"DE\"\n },\n \"contacts\": [\n {\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"[email protected]\",\n \"phone\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"companyNumber": "NT-4K7M-9XQ2",
"type": "COMPANY",
"companyName": "<string>",
"address": {
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"kycMode": "MARKETPLACE_LED",
"status": "ACTIVE",
"version": 123,
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"vatNumber": "<string>",
"inactiveSince": "2023-11-07T05:31:56Z",
"notApplied": [
"companyName"
]
}{
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"companyNumber": "NT-4K7M-9XQ2",
"type": "COMPANY",
"companyName": "<string>",
"address": {
"uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"street": "<string>",
"postCode": "<string>",
"city": "<string>",
"country": "DE"
},
"kycMode": "MARKETPLACE_LED",
"status": "ACTIVE",
"version": 123,
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"vatNumber": "<string>",
"inactiveSince": "2023-11-07T05:31:56Z",
"notApplied": [
"companyName"
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>",
"rejectedValue": "<unknown>"
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"traceId": "<string>"
}Authorizations
OAuth 2.0 client_credentials grant via Keycloak service account.
Clients are provisioned per IntegrationOrganization in the
nt24-idp realm.
Headers
Client-generated unique key (UUID recommended) for this write operation.
Identical key + identical body within 24h returns the cached response.
Identical key + different body returns 422.
1 - 255Path Parameters
UUID of the seller trade partner (must be in caller's authorized list).
Body
MARKETPLACE_LED (Mode A), partner provides KYC data; NT24 stores + runs VIES only.
Not available yet: a buyer write naming it is refused with 403.
NT24_LED (Mode B), NT24 runs full verification (iDenfy + AML) and generates a profile.
MARKETPLACE_LED, NT24_LED "DE123456789"
Show child attributes
Show child attributes
Contact people to record on the buyer. Each entry resolves on its
email address, compared trimmed and case-insensitively, exactly as
POST /contacts does: a person already recorded matches and no
second record is created, a new one is recorded, and a different
person under a recorded email address resolves to the person on
file. So a replayed create body leaves the contact list as it was,
and an archived person is reactivated rather than recorded again
(one with a platform account whom the company removed answers
409; they return only through the company's re-invite; a
reactivated owner stays owner only when the buyer has none and you
may name one).
Entries resolve in order, so a second entry under an address an
earlier entry already used is matched against what that earlier one
left. Where an entry disagrees with the person it resolved to, the
response names its fields in notApplied as
contacts[1].firstName — the index is the entry's position in the
array you sent. Nothing is applied: a stored person is never
overwritten here, and no second record is created under an address
one person already holds. Two different people on one address is
therefore reported rather than silently dropped, on the 201 as
well as on the 200.
An entry's role is asked for as on POST /contacts, in order: the
first entry asking for OWNER on a new buyer becomes its Admin, and
a later one finds the seat taken and is reported as
contacts[1].role.
Show child attributes
Show child attributes
Response
Existing buyer returned, unchanged. notApplied lists the fields of
the body the record disagreed with, and is absent when there were
none.
The Novatrade24-issued partner number. Every partner has one,
whatever its type, which is why this — and not vatNumber — is
the identifier that resolves any buyer.
^NT-[A-Z0-9]{4}-[A-Z0-9]{4}$"NT-4K7M-9XQ2"
Whether the party is a legal entity or a natural person. Shared by
BuyerResponse and ProspectResponse, which describe the same two
kinds of party at different points of the onboarding flow.
The webhook contract publishes these same two values as ProspectType
in asyncapi-integration-v1.yaml. Two names, one set of values.
COMPANY, CONSUMER The legal company name for a COMPANY buyer, the natural person's
full name for a CONSUMER buyer.
Show child attributes
Show child attributes
MARKETPLACE_LED (Mode A), partner provides KYC data; NT24 stores + runs VIES only.
Not available yet: a buyer write naming it is refused with 403.
NT24_LED (Mode B), NT24 runs full verification (iDenfy + AML) and generates a profile.
MARKETPLACE_LED, NT24_LED ACTIVE, BLOCKED Optimistic-concurrency version.
The buyer's VAT number, present only when the platform holds one.
Its presence does not classify the buyer: no onboarding path gives
a natural person a VAT number and a company may never have supplied
one, but neither is a guarantee this API makes. Read type for the
kind of party.
When the buyer closed its account on Novatrade24 and became inactive; null while it is active. An inactive buyer takes no new order, KYC request or invitation (409), and its existing orders are read-only.
Fields of an upsert body that named something other than what this record says, and were therefore not written. Two groups, in one list:
- the buyer's own fields —
companyName,address.street,address.postCode,address.city,address.country; - a
contactsentry that disagreed with the person already recorded under its email address —contacts[0].firstName,contacts[0].lastName,contacts[0].phone, where the index is the entry's position in the array you sent — or that asked for arolethe person did not get,contacts[0].role.
Present on POST /buyers only, and only when there was something to
report. Absent on every read and on a call that agreed with the
record. Absent on a 201 for the buyer's own fields — a buyer this
call just created cannot disagree with the body that created it —
but a contacts entry can still be reported on a 201, because an
array naming two different people on one email address contradicts
itself whatever the buyer's age.
Change a buyer field with PATCH under If-Match. A contact person
cannot be changed through this API at all; ask Novatrade24 support.
notApplied describes your request, not the resource, so it is not
part of the buyer's ETag and two callers reading the same buyer
still see the same tag.