API & externe koppelingen
De Tesoro Open API stelt u in staat om externe systemen, websites en applicaties te koppelen aan uw Tesoro CRM-omgeving. Via de API kunt u contacten, notities, bestanden en referentiewaarden beheren vanuit uw eigen software. De API is gebouwd met FastAPI (Python) en volgt RESTful-principes.
API-overzicht
Section titled “API-overzicht”De Open API biedt de volgende mogelijkheden:
| Functionaliteit | Beschrijving |
|---|---|
| Contactbeheer | Contacten aanmaken, opvragen, bijwerken en verwijderen. |
| Notitiebeheer | Notities koppelen aan contacten, vastpinnen en doorzoeken. |
| Bestandsupload | Bestanden uploaden naar CRM-resources (woningen, contacten, etc.). |
| Referentiewaarden | Enum-waarden ophalen voor contactvelden, landen, talen en woningtypes. |
| Audit logging | Automatische logging van alle API-verzoeken voor beveiliging en foutopsporing. |
API-client aanmaken
Section titled “API-client aanmaken”Elke externe koppeling vereist een API-client. Bij het aanmaken worden de volgende gegevens vastgelegd:
| Eigenschap | Beschrijving |
|---|---|
| Client ID | Uniek identificatienummer van 32 tekens (letters, cijfers, underscore en koppelteken). |
| Client Secret | Geheim wachtwoord, versleuteld opgeslagen via AES-encryptie en gehasht met Blake2b. |
| Naam | Herkenbare naam voor de koppeling (bijv. “Website contactformulier”). |
| Permissies | Welke acties de client mag uitvoeren (zie onderstaande tabel). |
| Rate limits | Maximaal aantal verzoeken per minuut, uur en dag. |
| IP-whitelist | Optionele lijst van toegestane IP-adressen. |
| Status | Status van de client: active, suspended of revoked. |
| Laatste gebruik | Tijdstempel van het laatste API-verzoek (automatisch bijgewerkt). |
Stappen om een API-client aan te maken
Section titled “Stappen om een API-client aan te maken”-
Ga naar Instellingen > API & Koppelingen in uw Tesoro CRM-omgeving.
-
Klik op Nieuwe API-client aanmaken.
-
Vul een herkenbare naam in voor de koppeling (bijv. “Website formulier” of “Externe makelaarskoppeling”).
-
Selecteer de benodigde permissies voor deze client.
-
Configureer optioneel de rate limits en IP-whitelist.
-
Klik op Opslaan. Uw Client ID en Client Secret worden getoond.
-
Kopieer het Client Secret direct en bewaar het op een veilige locatie.
Permissies
Section titled “Permissies”Elke API-client kan worden voorzien van een of meerdere permissies. Permissies bepalen welke endpoints toegankelijk zijn.
| Permissie | Beschrijving | Vereist voor endpoints |
|---|---|---|
contacts:read | Contacten opvragen en doorzoeken. | GET /contacts, GET /contacts/{id} |
contacts:write | Contacten aanmaken, bijwerken en verwijderen. | POST /contacts, PUT /contacts/{id}, DELETE /contacts/{id} |
notes:read | Notities opvragen bij contacten. | GET /notes, GET /notes/{id} |
notes:write | Notities aanmaken, bijwerken, verwijderen en vastpinnen. | POST /notes, PUT /notes/{id}, DELETE /notes/{id}, POST /notes/{id}/pin |
Authenticatie
Section titled “Authenticatie”Alle API-verzoeken (behalve het bestandsupload-endpoint) worden geauthenticeerd via een CLIENT-ID-header.
-
Voeg de header
CLIENT-IDtoe aan elk verzoek met daarin uw Client ID (32 tekens). -
De API valideert uw client, controleert of de client actief is en haalt de bijbehorende permissies op.
-
Bij succesvolle authenticatie worden de permissies, rate limits en IP-whitelist van uw client toegepast.
-
De
last_used_at-tijdstempel van de client wordt automatisch bijgewerkt.
Authenticatievoorbeeld
Section titled “Authenticatievoorbeeld”curl -X GET "https://api.tesoro.example.com/api/v1/contacts" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__" \ -H "Content-Type: application/json"const response = await fetch("https://api.tesoro.example.com/api/v1/contacts", { method: "GET", headers: { "CLIENT-ID": "uw_32_karakter_client_id_hier__", "Content-Type": "application/json" }});
const data = await response.json();import requests
headers = { "CLIENT-ID": "uw_32_karakter_client_id_hier__", "Content-Type": "application/json"}
response = requests.get( "https://api.tesoro.example.com/api/v1/contacts", headers=headers)
data = response.json()$ch = curl_init();curl_setopt($ch, CURLOPT_URL, "https://api.tesoro.example.com/api/v1/contacts");curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_HTTPHEADER, [ "CLIENT-ID: uw_32_karakter_client_id_hier__", "Content-Type: application/json"]);
$response = curl_exec($ch);curl_close($ch);
$data = json_decode($response, true);Responsformaat
Section titled “Responsformaat”Alle API-endpoints retourneren een gestandaardiseerd JSON-formaat.
Succes-respons (enkel item)
Section titled “Succes-respons (enkel item)”{ "result": true, "message": "Contact has been created successfully", "data": { "_id": "507f1f77bcf86cd799439011", "first_name": "Jan", "last_name": "De Vries", "email": "jan@voorbeeld.nl", "status": "new", "type": "lead", "company_id": "507f1f77bcf86cd799439000", "created_at": 1709395200, "updated_at": 1709395200 }}Succes-respons (lijst met paginering)
Section titled “Succes-respons (lijst met paginering)”{ "result": true, "message": "Contact has been fetched correctly", "data": { "models": [ { "_id": "507f1f77bcf86cd799439011", "first_name": "Jan", "last_name": "De Vries", "email": "jan@voorbeeld.nl" } ], "pagination": { "page": 1, "pageCount": 5, "pageSize": 20, "totalCount": 94 } }}Fout-respons
Section titled “Fout-respons”{ "result": false, "message": "Validation failed", "error_code": "VALIDATION_ERROR", "status_code": 400, "data": null, "errors": [ { "code": "INVALID_FORMAT", "field": "email", "details": "Invalid email format" } ]}Beschikbare endpoints
Section titled “Beschikbare endpoints”Contacten
Section titled “Contacten”| Methode | Endpoint | Beschrijving | Permissie |
|---|---|---|---|
POST | /api/v1/contacts | Nieuw contact aanmaken. | contacts:write |
GET | /api/v1/contacts | Contacten opvragen met paginering en filters. | contacts:read |
GET | /api/v1/contacts/{id} | Een specifiek contact opvragen op basis van ID. | contacts:read |
PUT | /api/v1/contacts/{id} | Contact bijwerken. | contacts:write |
DELETE | /api/v1/contacts/{id} | Contact verwijderen (soft delete). | contacts:write |
Queryparameters voor contactenlijst
Section titled “Queryparameters voor contactenlijst”| Parameter | Type | Standaard | Beschrijving |
|---|---|---|---|
page | integer | 1 | Paginanummer (minimaal 1). |
limit | integer | 20 | Aantal resultaten per pagina (1-100). |
search | string | - | Zoektekst (doorzoekt naam, e-mail, etc.). |
status | string | - | Filtert op contactstatus (bijv. new, active). |
type | string | - | Filtert op contacttype (bijv. lead, client). |
role | string | - | Filtert op rol. |
lead_source | string | - | Filtert op leadbron. |
lead_stage | string | - | Filtert op leadfase. |
Contactvelden
Section titled “Contactvelden”Bij het aanmaken van een contact (POST) zijn de volgende velden beschikbaar:
| Veld | Type | Verplicht | Beschrijving |
|---|---|---|---|
first_name | string | Ja | Voornaam van het contact. |
last_name | string | Ja | Achternaam van het contact. |
email | string | Ja | E-mailadres (wordt gevalideerd). |
phone | string | Nee | Telefoonnummer met landcode (bijv. +31612345678). |
salutation | string | Nee | Aanhef (enum-waarde). |
language | string | Nee | Taalcode (standaard en). |
status | string | Nee | Status (standaard new). |
role | string | Nee | Rol van het contact (enum-waarde). |
type | string | Nee | Type contact (standaard lead). |
additional_information | string | Nee | Aanvullende informatie. |
address | object | Nee | Adresgegevens (street, city, zip_code, country, etc.). |
property_address | object | Nee | Adres van het vastgoedobject. |
lead | object | Nee | Leadgegevens (status, source, stage, source_mls). |
property_details | object | Nee | Vastgoeddetails (type, locatie, slaapkamers, prijs, etc.). |
timing | object | Nee | Timing (looking_to, timeline_max). |
way_of_selling | string | Nee | Verkoopwijze (enum-waarde). |
commision | float | Nee | Commissiepercentage (0-100). |
Bij het bijwerken (PUT) zijn alle velden optioneel.
Notities
Section titled “Notities”| Methode | Endpoint | Beschrijving | Permissie |
|---|---|---|---|
POST | /api/v1/notes | Notitie aanmaken bij een contact. | notes:write |
GET | /api/v1/notes | Notities opvragen voor een contact (met paginering, zoeken, sortering en filter op vastgepinde notities). | notes:read |
GET | /api/v1/notes/{id} | Een specifieke notitie opvragen. | notes:read |
PUT | /api/v1/notes/{id} | Notitie bijwerken. | notes:write |
DELETE | /api/v1/notes/{id} | Notitie verwijderen. | notes:write |
POST | /api/v1/notes/{id}/pin | Vastpinstatus van een notitie wisselen. | notes:write |
Queryparameters voor notitielijst
Section titled “Queryparameters voor notitielijst”| Parameter | Type | Standaard | Beschrijving |
|---|---|---|---|
resource_name | string | contact | Type resource (momenteel alleen contact ondersteund). |
resource_id | string | verplicht | ID van de resource (bijv. contact-ID). |
page | integer | 1 | Paginanummer. |
limit | integer | 20 | Resultaten per pagina (1-100). |
search | string | - | Zoektekst. |
pinned_only | boolean | false | Alleen vastgepinde notities tonen. |
sort | string | -created_at | Sortering: -created_at (nieuwste eerst) of created_at (oudste eerst). |
Notitievelden
Section titled “Notitievelden”| Veld | Type | Verplicht | Beschrijving |
|---|---|---|---|
title | string | Ja | Titel van de notitie. |
content | string | Ja | Inhoud van de notitie. |
pinned | boolean | Nee | Vastpinstatus (standaard false). |
resource_name | string | Ja (bij aanmaken) | Type resource: contact. |
resource_id | string | Ja (bij aanmaken) | ID van de gekoppelde resource. |
Referentiewaarden (Enums)
Section titled “Referentiewaarden (Enums)”| Methode | Endpoint | Beschrijving | Permissie |
|---|---|---|---|
GET | /api/v1/enum/contact | Contactspecifieke enum-waarden ophalen (status, rol, type, leadbron, aanhef, etc.). | contacts:read |
GET | /api/v1/enum/shared | Gedeelde enum-waarden ophalen (landen, talen). | contacts:read |
GET | /api/v1/enum/property | Vastgoed enum-waarden ophalen (woningtypes). | contacts:read |
Bestanden
Section titled “Bestanden”| Methode | Endpoint | Beschrijving | Authenticatie |
|---|---|---|---|
POST | /api/v1/files/upload | Bestand uploaden naar een resource (woning, contact, etc.). | Bearer-token |
Upload-parameters
Section titled “Upload-parameters”| Parameter | Type | Verplicht | Beschrijving |
|---|---|---|---|
file | bestand | Ja | Het te uploaden bestand (multipart form-data). |
resource_name | string | Ja | Type resource (bijv. property, contact). |
resource_id | string | Nee | ID van de gekoppelde resource. |
file_title | string | Nee | Titel voor het bestand. |
sort | string | Nee | Sorteervolgorde. |
Statuscontrole
Section titled “Statuscontrole”| Methode | Endpoint | Beschrijving | Authenticatie |
|---|---|---|---|
GET | /health | Statuscontrole van de API. Retourneert de servicenaam en versie. | Geen |
Codevoorbeelden
Section titled “Codevoorbeelden”Contact aanmaken
Section titled “Contact aanmaken”curl -X POST "https://api.tesoro.example.com/api/v1/contacts" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jan", "last_name": "De Vries", "email": "jan@voorbeeld.nl", "phone": "+31612345678", "language": "nl", "status": "new", "type": "lead", "address": { "street": "Keizersgracht 123", "city": "Amsterdam", "zip_code": "1015 AA", "country": "NL" }, "lead": { "source": "website", "stage": "new" }, "property_details": { "type": ["apartment"], "number_of_bedrooms": 3, "price_min": 250000, "price_max": 500000 } }'const contact = { first_name: "Jan", last_name: "De Vries", email: "jan@voorbeeld.nl", phone: "+31612345678", language: "nl", status: "new", type: "lead", address: { street: "Keizersgracht 123", city: "Amsterdam", zip_code: "1015 AA", country: "NL" }, lead: { source: "website", stage: "new" }, property_details: { type: ["apartment"], number_of_bedrooms: 3, price_min: 250000, price_max: 500000 }};
const response = await fetch("https://api.tesoro.example.com/api/v1/contacts", { method: "POST", headers: { "CLIENT-ID": "uw_32_karakter_client_id_hier__", "Content-Type": "application/json" }, body: JSON.stringify(contact)});
const data = await response.json();
if (data.result) { console.log("Contact aangemaakt:", data.data._id);} else { console.error("Fout:", data.message);}import requests
headers = { "CLIENT-ID": "uw_32_karakter_client_id_hier__", "Content-Type": "application/json"}
contact = { "first_name": "Jan", "last_name": "De Vries", "email": "jan@voorbeeld.nl", "phone": "+31612345678", "language": "nl", "status": "new", "type": "lead", "address": { "street": "Keizersgracht 123", "city": "Amsterdam", "zip_code": "1015 AA", "country": "NL" }, "lead": { "source": "website", "stage": "new" }, "property_details": { "type": ["apartment"], "number_of_bedrooms": 3, "price_min": 250000, "price_max": 500000 }}
response = requests.post( "https://api.tesoro.example.com/api/v1/contacts", headers=headers, json=contact)
data = response.json()
if data["result"]: print(f"Contact aangemaakt: {data['data']['_id']}")else: print(f"Fout: {data['message']}")Contacten opvragen met filters
Section titled “Contacten opvragen met filters”# Alle leads ophalen, pagina 1, 10 resultaten per paginacurl -X GET "https://api.tesoro.example.com/api/v1/contacts?type=lead&page=1&limit=10" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__"
# Zoeken op naam of e-mailadrescurl -X GET "https://api.tesoro.example.com/api/v1/contacts?search=jan&status=new" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__"import requests
headers = {"CLIENT-ID": "uw_32_karakter_client_id_hier__"}
# Zoeken met filtersparams = { "type": "lead", "status": "new", "search": "jan", "page": 1, "limit": 10}
response = requests.get( "https://api.tesoro.example.com/api/v1/contacts", headers=headers, params=params)
data = response.json()pagination = data["data"]["pagination"]
print(f"Pagina {pagination['page']} van {pagination['pageCount']}")print(f"Totaal: {pagination['totalCount']} contacten")
for contact in data["data"]["models"]: print(f"- {contact['first_name']} {contact['last_name']} ({contact['email']})")Notitie aanmaken bij een contact
Section titled “Notitie aanmaken bij een contact”curl -X POST "https://api.tesoro.example.com/api/v1/notes" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__" \ -H "Content-Type: application/json" \ -d '{ "title": "Bezichtiging gepland", "content": "Bezichtiging gepland op 15 maart 2026 om 14:00 voor appartement aan de Herengracht.", "pinned": true, "resource_name": "contact", "resource_id": "507f1f77bcf86cd799439011" }'const note = { title: "Bezichtiging gepland", content: "Bezichtiging gepland op 15 maart 2026 om 14:00 voor appartement aan de Herengracht.", pinned: true, resource_name: "contact", resource_id: "507f1f77bcf86cd799439011"};
const response = await fetch("https://api.tesoro.example.com/api/v1/notes", { method: "POST", headers: { "CLIENT-ID": "uw_32_karakter_client_id_hier__", "Content-Type": "application/json" }, body: JSON.stringify(note)});
const data = await response.json();Enum-waarden ophalen
Section titled “Enum-waarden ophalen”# Contactspecifieke enum-waarden (status, rol, type, leadbron, etc.)curl -X GET "https://api.tesoro.example.com/api/v1/enum/contact" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__"
# Gedeelde waarden (landen, talen)curl -X GET "https://api.tesoro.example.com/api/v1/enum/shared" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__"
# Vastgoed enum-waarden (woningtypes)curl -X GET "https://api.tesoro.example.com/api/v1/enum/property" \ -H "CLIENT-ID: uw_32_karakter_client_id_hier__"Bestand uploaden
Section titled “Bestand uploaden”curl -X POST "https://api.tesoro.example.com/api/v1/files/upload" \ -H "Authorization: Bearer uw_bearer_token_hier" \ -F "file=@/pad/naar/document.pdf" \ -F "resource_name=property" \ -F "resource_id=507f1f77bcf86cd799439022" \ -F "file_title=Plattegrond begane grond"Rate limiting
Section titled “Rate limiting”Elke API-client heeft configureerbare rate limits. De standaardwaarden zijn:
| Limiet | Standaardwaarde | Redis-sleutel |
|---|---|---|
| Per minuut | 100 verzoeken | rate_limit:{client_id}:minute:{timestamp} |
| Per uur | 1.000 verzoeken | rate_limit:{client_id}:hour:{timestamp} |
| Per dag | 10.000 verzoeken | rate_limit:{client_id}:day:{timestamp} |
Bij overschrijding van een limiet ontvangt u een HTTP 429 Too Many Requests-fout:
{ "detail": "Minute rate limit exceeded. Limit: 100 requests"}IP-whitelisting
Section titled “IP-whitelisting”Optioneel kunt u een lijst van toegestane IP-adressen instellen per API-client. Wanneer er IP-adressen zijn geconfigureerd, worden verzoeken vanaf niet-whitelisted adressen geweigerd met een HTTP 403 Forbidden-fout.
De API ondersteunt de volgende IP-headers voor correcte identificatie achter een load balancer of proxy:
| Header | Beschrijving |
|---|---|
X-Forwarded-For | Standaard proxy-header (eerste IP-adres wordt gebruikt). |
X-Real-IP | Alternatieve proxy-header. |
| Directe verbinding | Fallback naar het IP-adres van de directe verbinding. |
Audit logging
Section titled “Audit logging”Alle API-verzoeken worden automatisch gelogd in de collectie api_audit_logs voor beveiligings- en foutopsporingsdoeleinden. Per verzoek worden de volgende gegevens vastgelegd:
| Veld | Beschrijving |
|---|---|
| Client ID | De client die het verzoek heeft uitgevoerd. |
| Bedrijf | Het gekoppelde bedrijf. |
| Endpoint | Het aangevraagde API-endpoint. |
| HTTP-methode | GET, POST, PUT of DELETE. |
| IP-adres | Het IP-adres van de aanvrager. |
| User Agent | De user-agent string van de aanvrager. |
| Verzoekgegevens | Geanonimiseerde versie van de request body (gevoelige velden worden vervangen door [REDACTED]). |
| Responsstatus | HTTP-statuscode van het antwoord. |
| Verwerkingstijd | Duur van de verwerking in milliseconden. |
| Fouten | Eventuele foutmeldingen. |
| Tijdstempel | Tijdstip van het verzoek. |
Foutcodes
Section titled “Foutcodes”De API retourneert gestandaardiseerde HTTP-statuscodes en foutberichten:
| Statuscode | Foutcode | Beschrijving |
|---|---|---|
400 | BAD_REQUEST / VALIDATION_ERROR | Ongeldig verzoek of validatiefout. |
401 | UNAUTHORIZED | Ontbrekende of ongeldige CLIENT-ID header. |
403 | FORBIDDEN | Onvoldoende permissies of IP niet gewhitelist. |
404 | NOT_FOUND | Resource niet gevonden. |
422 | UNPROCESSABLE_ENTITY | Onverwerkbare entiteit (schema-validatiefout). |
429 | - | Rate limit overschreden. |
500 | INTERNAL_SERVER_ERROR | Onverwachte serverfout. |
502 | PROXY_ERROR | Fout bij communicatie met de interne Tesoro API (alleen bij bestandsupload). |
Beveiligingstips
Section titled “Beveiligingstips”Interactieve documentatie
Section titled “Interactieve documentatie”De Open API biedt automatisch gegenereerde, interactieve documentatie via Swagger UI. Deze is beschikbaar op het /docs-endpoint van uw API-omgeving.
Via de Swagger UI kunt u:
- Alle beschikbare endpoints bekijken met hun parameters en response-schema’s.
- Uw
CLIENT-IDeenmalig invoeren via de Authorize-knop, waarna deze automatisch wordt meegezonden bij alle testverzoeken. - Endpoints direct testen vanuit uw browser.
- Response-voorbeelden en foutmeldingen inzien.
Veelgestelde vragen
Section titled “Veelgestelde vragen”Kan ik meerdere API-clients aanmaken? Ja. U kunt per koppeling een aparte client aanmaken met eigen permissies, rate limits en IP-whitelist. Dit wordt sterk aanbevolen voor het scheiden van verschillende integraties.
Wat gebeurt er als mijn client wordt opgeschort?
Een client met de status suspended of revoked kan geen API-verzoeken meer uitvoeren. Alle verzoeken worden geweigerd met een HTTP 403-fout.
Welke ID-formaat wordt gebruikt? De API maakt gebruik van MongoDB ObjectId’s (24-karakter hexadecimale strings). Bij het meegeven van een ongeldig ID-formaat ontvangt u een validatiefout.
Hoe werkt de eigenaar-toewijzing bij nieuwe contacten? Wanneer u een contact aanmaakt via de API, wordt automatisch een eigenaar toegewezen. De API haalt de standaard eigenaar op uit de websiteinstellingen van uw bedrijf. Als er geen standaard eigenaar is geconfigureerd, wordt de eerste gebruiker van het bedrijf toegewezen.
Kan ik notities koppelen aan andere resources dan contacten?
Momenteel ondersteunt de API alleen notities voor de resource contact. Ondersteuning voor andere resources (zoals woningen) kan in toekomstige versies worden toegevoegd.
Worden verwijderde contacten definitief verwijderd? Nee. Het DELETE-endpoint voert een soft delete uit. Het contact wordt gemarkeerd als verwijderd maar blijft in de database beschikbaar voor eventueel herstel.
Wat als de telefoonnummervalidatie mislukt?
Telefoonnummers moeten het internationale formaat volgen met landcode, beginnend met + (bijv. +31612345678). Nummers zonder landcode worden geweigerd.
Hoe kan ik de geldige waarden voor keuzevelden achterhalen?
Gebruik de enum-endpoints (/api/v1/enum/contact, /api/v1/enum/shared, /api/v1/enum/property) om alle geldige waarden en hun labels op te halen. Dit is bijzonder nuttig bij het bouwen van formulieren die contacten aanmaken via de API.