curl --request POST \
--url https://public.api.live.turrisfi.com/v2/downstream/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "Jon",
"lastName": "Doe",
"email": "example@test.com",
"downstreamEntityId": "6610b3d2c2e0a51b8c0d1f02",
"upstreamDownstreamAssociationId": "6627f1a9c2e0a51b8c0d4e3a",
"businessRoles": [
"Compliance Officer"
],
"isPrimary": false
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: 'Jon',
lastName: 'Doe',
email: 'example@test.com',
downstreamEntityId: '6610b3d2c2e0a51b8c0d1f02',
upstreamDownstreamAssociationId: '6627f1a9c2e0a51b8c0d4e3a',
businessRoles: ['Compliance Officer'],
isPrimary: false
})
};
fetch('https://public.api.live.turrisfi.com/v2/downstream/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://public.api.live.turrisfi.com/v2/downstream/contacts"
payload = {
"firstName": "Jon",
"lastName": "Doe",
"email": "example@test.com",
"downstreamEntityId": "6610b3d2c2e0a51b8c0d1f02",
"upstreamDownstreamAssociationId": "6627f1a9c2e0a51b8c0d4e3a",
"businessRoles": ["Compliance Officer"],
"isPrimary": False
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"contactId": "6627f2b5c2e0a51b8c0d4e7c",
"firstName": "Jon",
"lastName": "Doe",
"email": "example@test.com",
"isDeliverable": true,
"scopes": [
{
"downstreamEntityId": "6610b3d2c2e0a51b8c0d1f02",
"businessRoles": [
"Finance Manager"
],
"isPrimary": true,
"upstreamDownstreamAssociationId": "6627f1a9c2e0a51b8c0d4e3a"
}
],
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-06-02T08:12:44.000Z"
},
"requestId": "dev-2c5e7cf2-9acf-4c8c-ab2f-b81f39d775a8",
"timestamp": "2025-11-12T20:49:03.293Z"
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}Add a Contact
Add a person to one of your entities, optionally scoped to one market
curl --request POST \
--url https://public.api.live.turrisfi.com/v2/downstream/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "Jon",
"lastName": "Doe",
"email": "example@test.com",
"downstreamEntityId": "6610b3d2c2e0a51b8c0d1f02",
"upstreamDownstreamAssociationId": "6627f1a9c2e0a51b8c0d4e3a",
"businessRoles": [
"Compliance Officer"
],
"isPrimary": false
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: 'Jon',
lastName: 'Doe',
email: 'example@test.com',
downstreamEntityId: '6610b3d2c2e0a51b8c0d1f02',
upstreamDownstreamAssociationId: '6627f1a9c2e0a51b8c0d4e3a',
businessRoles: ['Compliance Officer'],
isPrimary: false
})
};
fetch('https://public.api.live.turrisfi.com/v2/downstream/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://public.api.live.turrisfi.com/v2/downstream/contacts"
payload = {
"firstName": "Jon",
"lastName": "Doe",
"email": "example@test.com",
"downstreamEntityId": "6610b3d2c2e0a51b8c0d1f02",
"upstreamDownstreamAssociationId": "6627f1a9c2e0a51b8c0d4e3a",
"businessRoles": ["Compliance Officer"],
"isPrimary": False
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"contactId": "6627f2b5c2e0a51b8c0d4e7c",
"firstName": "Jon",
"lastName": "Doe",
"email": "example@test.com",
"isDeliverable": true,
"scopes": [
{
"downstreamEntityId": "6610b3d2c2e0a51b8c0d1f02",
"businessRoles": [
"Finance Manager"
],
"isPrimary": true,
"upstreamDownstreamAssociationId": "6627f1a9c2e0a51b8c0d4e3a"
}
],
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-06-02T08:12:44.000Z"
},
"requestId": "dev-2c5e7cf2-9acf-4c8c-ab2f-b81f39d775a8",
"timestamp": "2025-11-12T20:49:03.293Z"
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}{
"statusCode": 123,
"requestId": "<string>",
"errorType": "validation_error",
"errorMessage": [
"<string>"
],
"timestamp": "<string>",
"details": {}
}upstreamDownstreamAssociationId for an agency-wide contact that every one of your markets can see; supply it to confine the contact to one market relationship. When upstreamDownstreamAssociationId is given, the contact is added to that market’s contacts.
When you name a market, the contact belongs to the entity that holds that market. Leave downstreamEntityId out and that entity is used. If you send a different downstreamEntityId, the request is refused with 400 and nothing is written.
Email addresses are unique across Turris, and this is the part to read
Contact email is unique platform-wide, not per agency. So an address you add may already belong to a contact another agency introduced first. When that happens, this endpoint attaches a new scope for your entity to the existing contact and returns it as normal. The response is indistinguishable from having created a fresh contact: it shows only your own scopes, and it reflects the name you sent.Your name is yours only
firstName and lastName stay as the first agency recorded them — a create must not rename a record two agencies share. You will not see theirs; the response echoes what you sent. Treat it as authoritative for your scopes, not as a read of the database.A duplicate scope is a 409
A market has one primary contact
isPrimary: true on a market that already has a primary contact returns 409, and nothing is written. Change the primary contact in Turris, then add this contact without isPrimary. On a market with no primary contact, isPrimary: true is accepted and makes this contact the market’s primary.
This check reads only your own market, the one upstreamDownstreamAssociationId names, so it tells you nothing you cannot already see on that market’s contacts.
Send an Idempotency-Key header. See Idempotency.Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
UUID to ensure idempotent request processing
"550e8400-e29b-41d4-a716-446655440000"
Alternative UUID header for idempotent request processing
"550e8400-e29b-41d4-a716-446655440000"
Body
Given name. Stored only when this email is new to Turris; on an address the platform already knows, the existing record keeps its name and this value is echoed back to you unchanged.
"Jon"
Surname. Stored only when this email is new to Turris; on an address the platform already knows, the existing record keeps its name and this value is echoed back to you unchanged.
"Doe"
Email address. Unique across Turris and matched case-insensitively, so an address already known to the platform is attached to your agency rather than duplicated.
"example@test.com"
Which of your entities this contact belongs to. With upstreamDownstreamAssociationId, defaults to the entity that holds that market, and a different entity is refused with 400. Without it, defaults to your own organization.
"6610b3d2c2e0a51b8c0d1f02"
Confine the contact to one market relationship. Omit for an agency-wide contact, which every one of your markets can see.
"6627f1a9c2e0a51b8c0d4e3a"
Business roles this contact holds within the scope being created.
Compliance Officer, Principal Owner, Account Manager, Finance Manager, Support Staff, Contract Signatory, Accounts Payable, Policy Maintenance ["Compliance Officer"]
Whether this is the primary contact for the scope being created. With upstreamDownstreamAssociationId, true is refused with 409 when that market already has a primary contact.
false
Was this page helpful?